home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-06-06 | 230.5 KB | 5,691 lines |
- .co This is the `source form' of the user documentation for Gofer, to be
- .co processed using my simple prn formatter before being printed.
- .co
- .co Mark P. Jones August 1991
- .co-------------------------------------------------------------------|
- .>ch00
- .op
- .ti Introduction to Gofer
-
-
-
-
-
-
- __________ __________ __________ __________ ________
- / _______/ / ____ / / _______/ / _______/ / ____ \
- / / _____ / / / / / /______ / /______ / /___/ /
- / / /_ / / / / / / _______/ / _______/ / __ __/
- / /___/ / / /___/ / / / / /______ / / \ \
- /_________/ /_________/ /__/ /_________/ /__/ \__\
-
- Functional programming environment, Version 2.20
-
- Copyright Mark P Jones 1991.
-
-
-
-
-
-
- A N I N T R O D U C T I O N T O G O F E R
-
-
- draft version only --- please report any errors, suggestions for
- improvements, extensions (or deletions!) to jones-mark@cs.yale.edu
-
-
- This version includes a number of small corrections
- made since the original release.
-
- --------------------------------------------------------------------
- Permission to use, copy, modify, and distribute this software and its
- documentation for any personal or educational use without fee is hereby
- granted, provided that:
- a) This copyright notice is retained in both source code and
- supporting documentation.
- b) Modified versions of this software are redistributed only if
- accompanied by a complete history (date, author, description) of
- modifications made; the intention here is to give appropriate
- credit to those involved, whilst simultaneously ensuring that any
- recipient can determine the origin of the software.
- c) The same conditions are also applied to any software system
- derived either in full or in part from Gofer.
-
- The name "Gofer" is not a trademark, registered or otherwise, and
- you are free to mention this name in published material, public and
- private correspondence, or other documents without restriction or
- obligation.
-
- Gofer is provided "as is" without express or implied warranty.
- --------------------------------------------------------------------
- .pa
- .po
- .pn -100
- T A B L E O F C O N T E N T S
-
-
- .in contents
- .pa
- .pn 1
- .co-------------------------------------------------------------------|
- .>ch01
- .ST 1. INTRODUCTION
-
- Gofer is a functional programming environment (in other words, an
- interpreter) that I have implemented for my own personal use as part of
- my research into `qualified types'. Nevertheless, the system is
- sufficiently complete for me to believe that Gofer may be of interest
- and use to others interested in the field of functional programming.
-
- These notes give a brief introduction to the Gofer system and include
- some examples of Gofer programs. They are not the notes that I
- originally intended to write, being somewhat longer and perhaps more
- tutorial in nature. Nevertheless, you will not be able to learn
- functional programming from this document alone. A number of useful
- references are given in the reading list at the end of this document.
- In particular, the book by Bird and Wadler [1] is particularly good as
- a general introduction to the use, techniques and theory of functional
- programming. Although their notation is a little different from the
- language used by Gofer, it is a relatively straightforward task to
- translate between the two, and some suggestions for this are given in a
- appendix D. More importantly, the underlying semantics of Gofer do
- correspond to those expected by the authors of [1].
-
- Whereas the work involved in investigating and implementing the ideas
- on which Gofer is based were motivated largely by my own program of
- work, the writing of these notes has rather more to do with the hope
- that Gofer will be useful to others. I would therefore be very
- grateful for any feedback on any aspect of the these notes (or of the
- Gofer system itself). Please let me know if you discover any errors,
- or if you find particular sections of these notes rather hard to
- follow. Suggestions for improvements or extensions are more than
- welcome.
- .pa
- .co-------------------------------------------------------------------|
- .>ch02
- .ST 2. BACKGROUND AND ACKNOWLEDGEMENTS
-
- The language supported by Gofer is both syntactically and semantically
- similar to that of the functional programming language Haskell [5]. My
- principal task in the implementation of Gofer has therefore been to
- decide which features I should omit and then to implement what
- remains. Features common to both include:
-
- o Non-strict semantics (lazy evaluation).
- o Higher-order functions.
- o Extended polymorphic type system with support for user-defined
- overloading.
- o User-defined algebraic datatypes.
- o Pattern matching.
- o List comprehensions.
- o Facilities for I/O, whilst retaining referential transparency
- within a program.
-
- For the benefit of readers familiar with Haskell, the following
- features of Haskell are not supported in the standard version of Gofer:
-
- o Modules.
- o Arrays.
- o Defaults for unresolved overloading.
- o Derived instances of standard classes.
- o Contexts in datatype definitions.
- o Full range of numeric types and classes.
-
- But Gofer is not just a partial implementation of Haskell; it also
- includes a number of experimental features which extend the type system
- in several ways:
-
- o An alternative approach to type classes which avoids the need for
- construction of dictionaries during the evaluation of an
- expression.
- o Type classes may take multiple parameters.
- o Instances of type classes may be defined at arbitrary
- non-overlapping types.
- o Contexts may include arbitrary type expressions.
-
- These extensions stem from my own research [8, 9, 10, 11, 12] and were
- among the principal motivations for the development of Gofer. Full
- details of the differences between Gofer and Haskell 1.1 are given in
- appendix C.
-
- Gofer would not have been implemented without my original introduction
- to functional programming using Orwell [6], and I am particularly
- grateful to Quentin Miller for answering so many of my questions about
- functional programming and about the Orwell system in particular. I
- should also like to mention the influence of the Haskell B. compiler
- from Lennart Augustsson and Thomas Johnsson and based on their earlier
- LML compiler [7].
-
- Right from the beginning, I wanted to be able to use Gofer on a range
- of machines - and in particular, on the humble PC that I use at home.
- With this in mind, Gofer was actually developed on that same PC using
- Borland's Turbo C 1.5 and a public domain version of the yacc parser
- generator that I picked up some time ago. Gofer was also written with
- some degree of portability in mind and has subsequently been compiled
- to run on Sun workstations. I hope it will also be possible to port it
- to other platforms. It is my intention that Gofer be distributed
- complete with source code and I hope that this will be of interest to
- some users.
-
- Many of the ideas used in the back-end of the Gofer system (i.e. the
- compiler and abstract machine) originate from the chapters of Simon
- Peyton Jones textbook [2]; I very much doubt whether Gofer would have
- been completed without frequent reference to that book. The
- lambda-lifter used in Gofer is based on Thomas Johnsson's algorithm
- described in [3].
-
- On the theoretical side, I'm grateful to Phil Wadler for the
- encouragement that he has given me with my work on qualified types.
- Many of the basic ideas that I have used were inspired by his original
- paper motivating the use of type classes [4].
- .pa
- .co-------------------------------------------------------------------|
- .>ch03
- .ST 3. STARTING GOFER
-
- The Gofer interpreter is usually entered by giving the command `gofer',
- after which a display something like the following will normally be
- produced:
-
- Gofer Version 2.20
-
- Reading script file "/gofer/prelude":
- Parsing........................................................
- Dependency analysis............................................
- Type checking..................................................
- Compiling......................................................
-
- Gofer session for:
- /gofer/prelude
- Type :? for help
- ?
-
- The file name "/gofer/prelude" mentioned in the output above is the
- name of a file of standard definitions which are loaded into Gofer each
- time that the interpreter is started. By default, Gofer reads these
- definitions from a file called "prelude" in the current working
- directory. Alternatively you can set the environment variable GOFER to
- the name of the standard prelude file, which will then be used,
- whatever the current working directory might be.
-
- Most commands in Gofer take the form of a colon followed by one or more
- characters which distinguish one command from another. There are two
- commands which are particularly worth remembering:
-
- o :q exits the Gofer interpreter. On most systems, you can also
- exit from Gofer by typing the end of file character (^Z on an
- MS-DOS machine, usually ^D on a unix based machine).
-
- o :? prints a list of all the commands, which can be useful if you
- forget the name of the command that you want to use.
-
- The complete range of commands supported by the Gofer interpreter is
- described in appendix F.
-
- Note that the interrupt key (^C on most systems) can be used at any
- time whilst using Gofer to abandon the process of reading in a file of
- function definitions or the evaluation of an expression. When the
- interrupt key is detected, Gofer prints the string "{Interrupted!}" and
- prints the "? " prompt so that further commands can be entered.
-
- .pa
- .co-------------------------------------------------------------------|
- .>ch04
- .ST 4. USING GOFER - A BASIC INTRODUCTION
-
- Using Gofer is rather like using a high-level programmable calculator;
- Once the interpreter is loaded, the system prints a prompt "?" and
- waits for you to enter an expression, and then press the enter (return)
- key. Once the input is complete, Gofer evaluates the expression and
- prints its value on the terminal, before returning to the original
- prompt and waiting for the next expression. For example:
-
- ? (2+3)*8
- 40
- (5 reductions, 9 cells)
- ? sum [1..10]
- 55
- (91 reductions, 130 cells)
- ?
-
- In the first example, the user entered the expression "(2+3)*8", which
- was evaluated by Gofer and the result "40" printed on the terminal. At
- the end of any calculation, Gofer displays the number of reductions (a
- measure of the amount of work) and cells (a measure of the amount of
- memory) that were used during the calculation. These figures can be
- useful for comparing the performance of different ways of carrying out
- the same calculation.
-
- In the second example, the user typed the expression "sum [1..10]".
- The notation "[1..10]" represents the list of integers between 1 and 10
- inclusive, and "sum" is a standard function which can be used to
- determine the sum of a list of integers. Thus the result obtained by
- Gofer is:
-
- 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = 55
-
- We could have typed this sum into Gofer directly:
-
- ? 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10
- 55
- (10 reductions, 23 cells)
- ?
-
- and this calculation is certainly more efficient as it uses only 1/9th
- of the number of reductions and 1/5th of the number of cells as the
- original calculation. On the other hand, the original expression is
- much shorter and you are much less likely to make a mistake typing in
- the expression "sum [1..200]" than you would be if you tried to enter
- the sum of the integers from 1 to 200 directly.
-
- You will learn more about the kind of expressions that can be entered
- into Gofer in the rest of this document.
- .pa
- .co-------------------------------------------------------------------|
- .>ch05
- .ST 5. STANDARD AND USER-DEFINED FUNCTIONS
-
- The function "sum" used in the examples above, and indeed the addition
- and multiplication functions (+) and (*), are all standard functions
- which are included as part of a large collection of functions called
- the `standard prelude' which are loaded into the Gofer system each time
- that you start the interpreter. Quite a number of useful calculations
- can be carried out using these functions alone, but for more general
- use you can also define your own functions and store the definitions in
- a file so that these functions can be loaded and used by by the Gofer
- system. For example, suppose that you create a file "fact" containing
- the following definition:
-
- fact n = product [1..n]
-
- The "product" function is another standard function which can be used
- to calculate the product of a list of integers, and so the line above
- defines a function "fact" which calculates the factorial of its
- argument. In standard mathematical notation, fact n = n! which is
- usually defined informally by an equation of the form:
-
- n! = 1 * 2 * ... * (n-1) * n
-
- Once you become familiar with the notation used by Gofer, you will see
- that the Gofer definition of the factorial function is really very
- similar to this informal mathematical definition.
-
- In order to use this definition from the Gofer interpreter, we must
- first load the definitions of the file into the interpreter. The
- simplest way to do this uses the ":l" command:
-
- ? :l fact
- Reading script file "fact":
- Parsing......................................................
- Dependency analysis..........................................
- Type checking................................................
- Compiling....................................................
-
- Gofer session for:
- /gofer/prelude
- fact
- ?
-
- Notice the list of filenames displayed after "Gofer session for:"; this
- tells you which files of definitions are currently being used by Gofer,
- the first of which is the file containing the definitions for the
- standard prelude. Since the file containing the definition of the
- factorial function has now been loaded, we can make use of this
- function in expressions entered to the interpreter:
-
- ? fact 6
- 720
- (57 reductions, 85 cells)
-
- For another example, recall the standard mathematical formula which
- tells us that the number of ways of choosing r objects from a
- collection of n objects is given by n! / (r! * (n-r)!). In Gofer, this
- function can be defined by:
-
- comb n r = fact n /(fact r * fact (n-r))
-
- In order to use this function, we can either edit the file "fact" which
- contains the definition of the factorial function, adding the
- definition of "comb" on a new line, or we can include the definition as
- part of an expression entered whilst using Gofer:
-
- ? comb 5 2 where comb n r = fact n /(fact r * fact (n-r))
- 10
- (110 reductions, 161 cells)
- ?
-
- The ability to define a function as part of an expression like this is
- often quite useful. However, if the function "comb" were likely to be
- wanted on a number of occasions, it would be more sensible to add its
- definition to the contents of the file "fact", instead of having to
- repeat the definition each time it is used.
-
- You will learn more about the functions defined in the standard prelude
- and find out how to define your own functions in the following
- sections.
- .pa
- .co-------------------------------------------------------------------|
- .>ch06
- .ST 6. FUNCTION NAMES - IDENTIFIERS AND OPERATORS
-
- As the examples of the previous section show, there are two kinds of
- name that can be used for a function; identifiers such as "sum" and
- operator symbols such as "+" and "*". Choosing the appropriate kind of
- name for a particular function can often help to make expressions
- involving that function easier to read. If for example the addition
- function was represented by the name "plus" rather than the operator
- symbol "+" then the sum of the integers from 1 to 5 would have to be
- written as:
-
- plus (plus (plus (plus 1 2) 3) 4) 5
-
- In this particular case, another way of writing the same sum is:
-
- plus 1 (plus 2 (plus 3 (plus 4 5)))
-
- Not only does the use of the identifier "plus" make these expressions
- larger and more difficult to read than the equivalent expressions using
- "+"; it also makes it very much harder to see that these two
- expressions do actually have the same value.
-
- Gofer distinguishes between the two types of name according to the way
- that they are written:
-
- o An identifier begins with a letter of the alphabet optionally
- followed by a sequence of characters, each of which is either a
- letter, a digit, an apostrophe (') or an underbar (_).
- Identifiers representing functions or variables must begin with a
- lower case letter (identifiers beginning with an upper case letter
- are used to denote a special kind of function called a
- `constructor function' described in section 11.1). The following
- identifiers are examples of Gofer variable and function names:
-
- sum f f'' integerSum african_queen do'until'zero
-
- The following identifiers are reserved words in Gofer and cannot
- be used as the name of a function or variable:
-
- case of where let in if
- then else data type infix infixl
- infixr primitive class instance
-
- o An operator symbol is written using one or more of the following
- symbol characters:
-
- : ! # $ % & * + . / < = > ? @ \ ^ | -
-
- In addition, the tilde character (~) is also permitted, although
- only in the first position of an operator name. [N.B. Haskell
- also makes the same restriction for the minus/dash character
- (-)]. Operator names beginning with a colon are used for
- constructor functions in the same way as identifiers beginning
- with a capital letter as mentioned above. In addition, the
- following operator symbols have special uses in Gofer:
-
- :: = .. @ \ | <- -> ~ =>
-
- All other operator symbols can be used as variables or function
- names, including each of the following examples:
-
- + ++ && || <= == /= // .
- ==> $ @@ -*- \/ /\ ... ?
-
- [Note that each of the symbols in the first line is used in the
- standard prelude. If you are interested in using Gofer to develop
- programs for use with a Haskell compiler, you might also want to
- avoid using the operator symbols := ! :+ and :% which are used to
- support features in Haskell not currently provided by the Gofer
- standard prelude.]
-
- Gofer provides two simple mechanisms which make it possible to use an
- identifier as an operator symbol, or an operator symbol as an
- identifier:
-
- o Any identifier will be treated as an operator symbol if it is
- enclosed in backquotes (`) -- for example, the expressions using
- the "plus" function above are a little easier to read using this
- technique:
-
- (((1 `plus` 2) `plus` 3) `plus` 4) `plus` 5
-
- In general, an expression of the form "x `op` y" is equivalent to
- the corresponding expression "op x y", whilst an expression such
- as "f x y z" can also be written as "(x `f` y) z".
-
- [NOTE: For those using Gofer on a PC, you may find that your
- keyboard does not have a backquote key! In this case you should
- still be able to enter a backquote by holding down the key marked
- ALT, pressing the keys '9' and then '6' on the numeric keypad and
- then releasing the ALT key.]
-
- o Any operator symbol can be treated as an identifier by enclosing
- it in parentheses. For example, the addition function denoted by
- the operator symbol "+" is often written as "(+)". Any expression
- of the form "x + y" can also be written in the form "(+) x y".
-
- There are two more technical problems which have to be dealt with when
- working with operator symbols:
-
- o Precedence: Given operator symbols (+) and (*), should "2 * 3 + 4"
- be treated as either "(2 * 3) + 4" or "2 * (3 + 4)"?
-
- This problem is solved by assigning each operator a precedence
- value (an integer in the range 0 to 9). In a situation such as
- the above, we simply compare the precedence values of the
- operators involved, and carry out the calculation associated
- with the highest precedence operator first. The standard
- precedence values for (+) and (*) are 6 and 7 respectively so that
- the expression above will actually be treated as "(2 * 3) + 4".
-
- o Grouping: The above rule is only useful when the operator symbols
- involved have distinct precedences. For example, should the
- expression "1 - 2 - 3" be treated as either "(1 - 2) - 3" giving a
- result of -4, or as "1 - (2 - 3)" giving a result of 2?
-
- This problem is solved by giving each operator a `grouping'
- (sometimes called its associativity). An operator symbol (-) is
- said to:
-
- o group to the left if "x - y - z" is treated as "(x - y) - z"
-
- o group to the right if "x - y - z" is treated as "x - (y - z)"
-
- A third possibility is that an expression of the form "x - y - z"
- is to be treated as ambiguous and will be flagged as a syntax
- error. In this case we say that the operator (-) is
- non-associative.
-
- The standard approach in Gofer is to treat (-) as grouping to the
- left so that "1 - 2 - 3" will actually be treated as "(1-2)-3".
-
- By default, every operator symbol in Gofer is treated as
- non-associative with precedence 9. These values can be changed by a
- declaration of one of the following forms:
-
- infixl digit ops to declare operators which group to the left
- infixr digit ops to declare operators which group to the right
- infix digit ops to declare non-associative operators
-
- In each of these declarations ops represents a list of one or more
- operator symbols separated by commas and digit is an integer between 0
- and 9 which gives the precedence value for each of the listed operator
- symbols. The precedence digit may be omitted in which case a value of
- 9 is assumed. There are a number of restrictions on the use of these
- declarations:
-
- o Operator declarations can only appear in files of function
- definitions which are loaded into Gofer; they cannot be entered
- directly whilst using the Gofer interpreter.
-
- o At most one operator declaration is permitted for any particular
- operator symbol (even if repeated declarations all specify the
- same precedence and grouping as the original declaration).
-
- o Any file containing a declaration for an operator precedence and
- grouping must also contain a (top-level) declaration for that
- operator.
-
- In theory, it is possible to use an operator declaration at any point
- in a file of definitions. In practice, it is sensible to ensure that
- each operator is declared before the symbol is used. One way to
- guarantee this is to place all operator declarations at the beginning
- of the file [this condition is enforced in Haskell]. Note that until
- an operator declaration for a particular symbol is encountered, any
- occurrence of that symbol will be treated as a non-associative operator
- with precedence 9.
-
- The following operator declarations are taken from the standard prelude:
-
- -- Operator precedence table
-
- infixl 9 !!
- infixr 9 .
- infixr 8 ^
- infixl 7 *
- infix 7 /, `div`, `rem`, `mod`
- infixl 6 +, -
- infix 5 \\
- infixr 5 ++, :
- infix 4 ==, /=, <, <=, >=, >
- infix 4 `elem`, `notElem`
- infixr 3 &&
- infixr 2 ||
-
- and their use is illustrated by the following examples:
-
- Expression: Equivalent to: Reasons:
- ----------- -------------- --------
- 1 + 2 - 3 (1 + 2) - 3 (+) and (-) have the same precedence
- and group to the left.
- x : ys ++ zs x : (ys ++ zs) (:) and (++) have the same precedence
- and group to the right
- x == y || z (x == y) || z (==) has higher precedence than (||).
- 3 * 4 + 5 (3 * 4) + 5 (*) has higher precedence than (+).
- y `elem` z:zs y `elem` (z:zs) (:) has higher precedence than elem.
- 12 / 6 / 3 syntax error ambiguous use of (/); could mean
- either (12/6)/3 or 12/(6/3).
-
- Note that function application always binds more tightly than any infix
- operator symbol. For example, the expression "f x + g y" is equivalent
- to "(f x) + (g y)". Another example which often causes problems is the
- expression "f x + 1", which is treated as "(f x) + 1" and not as
- "f (x+1)" as is sometimes expected.
- .pa
- .co-------------------------------------------------------------------|
- .>ch07
- .ST 7. BUILT-IN TYPES
-
- An important part of Gofer is the type system which is used to detect
- errors in expressions and function definitions. Starting with
- primitive expressions such as numeric constants, Gofer assigns a type
- to each expression that describes the kind of value represented by the
- expression.
-
- In general we write object :: type to indicate that a particular
- expression has the indicated type. For example:
-
- 42 :: Int indicating that 42 is an integer (Int is the
- name for the type of integer values).
-
- fact :: Int -> Int indicating that "fact" is a function which
- takes an integer argument and returns an
- integer value (its factorial).
-
- The most important property of the type system is that it is possible
- to determine the type of an expression without having to evaluate it.
- For example, the information given above is sufficient to determine
- that fact 42 :: Int without needing to calculate 42! first.
-
- Gofer has a wide range of built-in types, described in the following
- sections. In addition, Gofer also includes facilities for defining new
- types as well as types acting as abbreviations for complicated type
- expressions as described in section 11.
-
-
- .ST 7.1 Functions
- --------------
- If t1 and t2 are types then t1 -> t2 is the type of a function which,
- given an argument of type t1 produces a result of type t2. A function
- of type t1 -> t2 is said to have argument type t1 and result type t2.
-
- In mathematics, the result of applying a function f to an argument x is
- traditionally written as f(x). In many situations, these parentheses
- are unnecessary and may be omitted when using Gofer.
-
- e.g. if f :: t1 -> t2 and x :: t1 then f x is the result of applying
- f to x and has type t2.
-
-
- If t1, t2, ..., tn are type expressions then:
-
- t1 -> t2 -> ... -> tn
-
- can be used as an abbreviation for the type:
-
- t1 -> (t2 -> ( ... -> tn) ...)
-
- In a similar way, an expression of the form f x1 x2 ... xn is simply an
- abbreviation for the expression ( ... ((f x1) x2) ... xn).
-
- These two conventions allow us to deal with functions taking more than
- one argument rather elegantly. For example, the type of the addition
- function (+) is:
- Int -> Int -> Int
-
- In other words, "(+)" is a function which takes an integer argument and
- returns a value of type (Int -> Int). For example, "(+) 5" is the
- function which takes an integer value n and returns the value of the
- integer n plus 5. Hence "(+) 5 4", which is equivalent to "5 + 4",
- evaluates to the integer 9 as expected.
-
-
- .ST 7.2 Booleans
- -------------
- Represented by the type "Bool", there are two boolean values written as
- "True" and "False". The standard prelude includes several useful
- functions for manipulating boolean values:
-
- (&&), (||) :: Bool -> Bool -> Bool
-
- The value of the expression b && d is True if and only if both
- b and d are True. If b is False then d is not evaluated.
-
- The value of the expression b || d is True if either of b or d
- is True. If b is True then d is not evaluated.
-
- not :: Bool -> Bool
-
- The value of the expression not b is the opposite boolean value
- to that of b; not True = False, not False = True.
-
- Gofer includes a special form of `conditional expression' which enables
- an expression to select between two alternatives according to the value
- of a boolean expression:
-
- if b then t else f
-
- is an expression which is equivalent to t if b evaluates to True, or to
- f if b evaluates to False. Note that an expression of this form is
- only acceptable if b is an expression of type Bool and if the types of
- t and f are the same, in which case the whole expression also has that
- type.
-
-
- .ST 7.3 Integers
- -------------
- Represented by the type "Int", the integer type includes both positive
- and negative integers such as -273, 0 and 383. Like many computer
- systems, the range of integer values that can be used is restricted and
- calculations using large positive or negative numbers may lead to
- (undetected) overflow.
-
- A wide range of operators and functions are defined in the standard
- prelude for use with integers:
-
- (+) addition.
- (*) multiplication.
- (-) subtraction.
- (^) raise to power.
- negate unary negation. An expression of the form "-x" is treated
- as the expression "negate x".
- (/) integer division.
- div " "
- rem remainder, related to integer division by the law:
- (x `div` y)*y + (x `rem` y) == x
- mod modulo, like remainder except that the modulo has the same
- sign as the divisor.
- odd returns True if argument is odd, False otherwise.
- even returns True if argument is even, False otherwise.
- gcd returns the greatest common divisor of its two arguments.
- lcm returns the least common multiple of its two arguments.
- abs returns the absolute value of its argument.
- signum returns -1, 0 or 1 indicating that its argument is negative,
- zero or positive respectively.
-
- The less familiar operators are illustrated by the following
- identities:
-
- 3^4 == 81, 7 `div` 3 == 2, even 23 == False
- 7 `rem` 3 == 1, -7 `rem` 3 == -1, 7 `rem` -3 == 1
- 7 `mod` 3 == 1, -7 `mod` 3 == 2, 7 `mod` -3 == -2
- gcd 32 12 == 4, abs (-2) == 2, signum 12 == 1
-
-
- .ST 7.4 Floating point numbers
- ---------------------------
- Represented by the type "Float", elements of this type can be used to
- represent fractional values as well as very large or very small
- quantities. Such values are however usually only accurate to a fixed
- number of digits and rounding errors may occur in some calculations
- making significant use of floating point quantities. A numeric value
- in an input expression will only be treated as a floating point number
- if it either includes a decimal point such as 3.14159, or if the number
- is too large to be stored as a value of type Int. Scientific notation
- may also be used to enter floating point quantities; for example 1.0e3
- is equivalent to 1000.0, whilst 5.0e-2 is equivalent to 0.05.
-
- [N.B. floating point numbers are not included in all implementations of
- Gofer].
-
-
- .ST 7.5 Characters
- ---------------
- Represented by the type "Char", elements of this type represent
- individual characters such as those entered at a keyboard. Character
- values are written as single characters enclosed by apostrophe
- characters: e.g. 'a', '0', 'Z'. Some special characters must be
- entered using an `escape code'; each of these begins with a backslash
- character '\', followed by one or more characters to select the
- required character. Some of the most useful escape codes are:
-
- '\\' backslash
- '\'' apostrophe
- '\"' double quote
- '\n' newline
- '\b' or '\BS' backspace
- '\DEL' delete
- '\t' or '\HT' tab
- '\a' or '\BEL' alarm (bell)
- '\f' or '\FF' formfeed
-
- Additional escape characters include:
-
- '\^c' control character, where c is replaced by
- one of the characters:
- "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
- For example, '\^A' represents control-A
-
- '\number' representing the character with ASCII value
- specified by the given decimal 'number'.
-
- '\onumber' representing the character with ASCII value
- specified by the given octal 'number'.
-
- '\xnumber' representing the character with ASCII value
- specified by the given 'hexadecimal' number.
-
- '\name' named ASCII control character, where
- `name' is replaced by one of the standard
- ascii names e.g. `\DC3`.
-
- In contrast with some common languages (such as C, for example)
- character values are quite distinct from integers; however the standard
- prelude does include functions:
-
- ord :: Char -> Int
- chr :: Int -> Char
-
- which enable you to map a character to its corresponding ASCII value,
- or from an ASCII value to the corresponding character:
-
- ? ord 'a'
- 97
- (2 reductions, 6 cells)
- ? chr 65
- 'A'
- (2 reductions, 7 cells)
- ?
-
-
- .ST 7.6 Lists
- ----------
- If a is a type then [a] is the type whose elements are lists of values
- of type a. There are several ways of writing list expressions:
-
- o The simplest list of any type is the empty list, written [].
-
- o Non-empty lists can be constructed either by explicitly listing
- the members of the list (for example: [1,3,10]) or by adding a
- single element onto the front of another list using the (:)
- operator (pronounced "cons"). These notations are equivalent:
-
- [1,3,10] = 1 : [3,10] = 1 : 3 : [10] = 1 : 3 : 10 : []
-
- (the (:) operator groups to the right so 1 : 3 : 10 : [] is
- equivalent to (1:(3:(10:[]))) -- a list whose first element is 1,
- second element is 3 and last element is 10).
-
- The standard prelude includes a wide range of functions for
- calculations involving lists. For example:
-
- o length xs returns the number of elements in the list xs.
- o xs ++ ys returns the list of elements in xs followed by the
- elements in ys
- o concat xss returns the list of elements in each of the lists in
- xss
- o map f xs returns the list of values obtained by applying the
- function f to each of the values in the list xs in turn.
-
- Here are some examples using these functions:
-
- ? length [1,3,10]
- 3
- (15 reductions, 28 cells)
-
- ? [1,3,10] ++ [2,6,5,7]
- [1, 3, 10, 2, 6, 5, 7]
- (19 reductions, 77 cells)
-
- ? concat [[1], [2,3], [], [4,5,6]]
- [1, 2, 3, 4, 5, 6]
- (29 reductions, 93 cells)
-
- ? map ord ['H', 'e', 'l', 'l', 'o']
- [72, 101, 108, 108, 111]
- (22 reductions, 73 cells)
-
- ?
-
- Note that all of the elements in a list must be of the same type, so
- that an expression such as ['a', 2, False] is not permitted.
-
- [ASIDE: At this point it might be useful to mention an informal
- convention that is used by a number of functional programmers when
- choosing names for variables representing elements of lists, lists
- themselves, lists of lists and so on. If for example, a typical
- element of a list is called x, then it is often useful to use the name
- xs for a list of such values, suggesting that a list contains a number
- of "x"s. Similarly, a list of lists might be called xss. Once you
- have understood this convention it is much easier to remember the
- relationship between the variables in the expression (x:xs) than it
- would be if different names had been used such as (a:b).]
-
-
- .ST 7.7 Strings
- ------------
- A string is treated as a list of characters and the type String is
- simply an abbreviation for the type [Char]. Strings are written as
- sequences of characters enclosed between speech marks. All of the
- escape codes that can be used for characters may also be used in a
- string:
-
- ? "hello, world"
- hello, world
- (0 reductions, 13 cells)
-
- ? "hello\nworld"
- hello
- world
- (0 reductions, 12 cells)
- ?
-
- In addition, strings may contain the escape sequence "\&" which can be
- used to separate otherwise ambiguous pairs of characters within a
- string:
-
- e.g. "\123h" represents the string ['\123', 'h']
- "\12\&3h" represents the string ['\12', '3', 'h']
-
- A string expression may be spread over a number of lines using a gap --
- a non-empty sequence of space, tab and new line characters enclosed by
- backslash characters:
-
- ? "hell\ \o"
- hello
- (0 reductions, 6 cells)
- ?
-
- Notice that strings are printed differently from other values, which
- gives the programmer complete control over the format of the output
- produced by a program. The only values that Gofer can in fact display
- on the terminal are strings. If the type of an expression entered into
- Gofer is equivalent to String then the expression is printed directly
- by evaluating and printing each character in the list in sequence.
- Otherwise, the expression to be evaluated, e, is replaced by the
- expression show' e where show' is a built-in function (defined as part
- of the standard prelude) which converts any value to a printable
- representation. The only way of printing a string value in the same
- way as any other value is by explicitly using the show' function:
-
- ? show' "hello"
- "hello"
- (7 reductions, 24 cells)
- ?
-
- The careful reader may have been puzzled by the fact the number of
- reductions used in the first three examples above was zero. This is in
- fact quite correct since these expressions are constants and no further
- evaluation can be carried out. For constant expressions of any other
- type there will always be at least one reduction needed to print the
- value since the constant must first be translated to a printable
- representation using the show' function.
-
- Because strings are represented as lists of characters, all of the
- standard prelude functions for manipulating lists can also be used with
- strings:
-
- ? length "Hello"
- 5
- (22 reductions, 36 cells)
-
- ? "Hello, " ++ "world"
- Hello, world
- (8 reductions, 37 cells)
-
- ? concat ["super","cali","fragi","listic"]
- supercalifragilistic
- (29 reductions, 101 cells)
-
- ? map ord "Hello"
- [72, 101, 108, 108, 111]
- (22 reductions, 69 cells)
-
- ?
-
-
- .ST 7.8 Tuples and the unit type
- -----------------------------
- If t1, t2, ..., tn are types and n>=2, then there is a type of n-tuples
- written (t1, t2, ..., tn) whose elements are also written in the form
- (x1, x2, ..., xn) where the expressions x1, x2, ..., xn have types t1,
- t2, ..., tn respectively.
-
- e.g. (1, [2], 3) :: (Int, [Int], Int)
- ('a', False) :: (Char, Bool)
- ((1,2),(3,4)) :: ((Int, Int), (Int, Int))
-
- Note that, unlike lists, the elements in a tuple may have different
- types, although the number of elements in the tuple is fixed.
-
- The unit type is written () and has a single element which is also
- written as (). The unit type is of particular interest in theoretical
- treatments of the type system of Gofer, although you may occasionally
- find a use for it in practical programs.
- .pa
- .co-------------------------------------------------------------------|
- .>ch08
- .ST 8. ERRORS
-
- .ST 8.1 Errors detected on input
- -----------------------------
- After an expression has been entered, but before any attempt is made to
- evaluate it, Gofer carries out a number of checks to make sure that the
- expression that you typed does not contain any errors. Here are some
- examples of the kind of problem that might occur:
-
- o Syntax errors. The most common situation in which this happens is
- when you make a typing mistake, either leaving out some
- characters, or perhaps pressing the wrong keys instead. In the
- following example, the user has missed out a `[' character:
-
- ? sum 1..100]
- ERROR: Syntax error in input (unexpected `..')
- ?
-
- o Undefined variables. This happens when you enter an expression
- using a variable or function name that is not defined in any of
- the files of definitions loaded into Gofer. This can often mean
- that you have misspelt the name of a function, or that the files
- defining a function have not yet been loaded. For example:
-
- ? sum [1..n]
- ERROR: Undefined variable "n"
- ?
-
- o Type errors. Certain expressions are sensible only when the
- functions used in those expressions are applied to values of the
- appropriate type. For example, whilst the factorial function can
- be used to calculate the factorial of an integer, it is clearly
- meaningless to try to determine the factorial of a character
- value. This kind of problem can be detected using the types of
- the components of an expression. In the expression "fact 'A'", we
- can see that the argument 'A' has type Char which does not match
- the argument type Int of the factorial function. This error will
- be detected by Gofer if you try to evaluate the expression:
-
- ? fact 'A'
- ERROR: Type error in application
- *** expression : fact 'A'
- *** term : 'A'
- *** type : Char
- *** does not match : Int
-
- ?
-
-
- .ST 8.2 Errors during evaluation
- -----------------------------
- If no errors are detected in an input expression, Gofer then begins to
- evaluate that expression. Despite all of the checks that are carried
- out before the evaluation begins, it is still possible for an error to
- occur during the evaluation of an expression. A typical example of
- this is an attempt to divide a number by zero. In this case, Gofer
- prints the part of the expression being evaluated that caused the
- error, surrounded by braces `{' and `}':
-
- ? 3/0
- {primDivInt 3 0}
- (4 reductions, 30 cells)
- ?
-
- [The function "primDivInt" which appears here is a primitive function
- used to divide one integer (its first argument) by another (the
- second)]. If an error occurs in just one part of an expression, only
- the part causing the problem will be displayed:
-
- ? 4 + (5/0)
- {primDivInt 5 0}
- (5 reductions, 32 cells)
- ?
-
- A standard function called "error" is defined in the standard prelude
- which is often useful for ensuring that appropriate error messages are
- produced when an error occurs:
-
- ? error "Problem has occurred"
- {error "Problem has occurred"}
- (23 reductions, 99 cells)
- ?
- .pa
- .co-------------------------------------------------------------------|
- .>ch09
- .ST 9. MORE ABOUT VALUE DECLARATIONS
-
- .ST 9.1 Simple pattern matching
- ----------------------------
- Although the Gofer standard prelude includes many useful functions, you
- will usually need to define a collection of new functions for specific
- problems and calculations. The declaration of a function "f" usually
- takes the form of a number of equations of the form:
-
- f <pat1> <pat2> ... <patn> = <rhs>
-
- (or an equivalent expression, if "f" is written as by an operator
- symbol). Each of the expressions <pat1>, <pat2>, ..., <patn>
- represents an argument to the function "f" and is called a `pattern'.
- The number of such arguments is called the arity of "f". If "f" is
- defined by more than one equation then they must be entered together
- and each one must give the same arity for "f".
-
- When a function is defined by more than one equation, it will usually
- be necessary to evaluate one or more of the arguments to the function
- to determine which equation applies. This process is called
- `pattern-matching'. In all of the previous examples we have used only
- the simplest kind of pattern -- a variable. As an example, consider
- the factorial function defined in section 5:
-
- fact n = product [1..n]
-
- If we then wish to evaluate the expression "fact 6" we first match the
- expression "6" against the pattern "n" and then evaluate the expression
- obtained from "product [1..n]" by replacing the variable "n" with the
- expression "6". The process of matching the arguments of a function
- against the patterns in its definition and obtaining another expression
- to be evaluated is called a `reduction'. Using Gofer, it is easy to
- verify that the evaluation of "fact 6" takes one more reduction than
- that of "product [1..6]":
-
- ? fact 6
- 720
- (57 reductions, 85 cells)
- ? product [1..6]
- 720
- (56 reductions, 85 cells)
- ?
-
- Many kinds of constants such as the boolean values True and False can
- also be used in patterns, as in the following definition of the
- function "not" taken from the standard prelude:
-
- not True = False
- not False = True
-
- In order to determine the value of an expression of the form "not b",
- we must first evaluate the expression "b". If the result is "True"
- then we use the first equation and the value of "not b" will be
- "False". If the value of "b" is "False", then the second equation is
- used and the value of "not b" will be "True".
-
- Other constants, including integers, characters and strings may also be
- used in patterns. For example, if we define a function "hello" by:
-
- hello "Mark" = "Howdy"
- hello name = "Hello " ++ name ++ ", nice to meet you!"
-
- then:
-
- ? hello "Mark"
- Howdy
- (1 reduction, 12 cells)
- ? hello "Fred"
- Hello Fred, nice to meet you!
- (13 reductions, 66 cells)
- ?
-
- Note that the order in which the equations are written is very
- important because Gofer always uses the first applicable equation. If
- instead we had defined the function with the equations:
-
- hello name = "Hello " ++ name ++ ", nice to meet you!"
- hello "Mark" = "Howdy"
-
- then the results obtained using this function would have been a little
- different:
-
- ? hello "Mark"
- Hello Mark, nice to meet you!
- (13 reductions, 66 cells)
- ? hello "Fred"
- Hello Fred, nice to meet you!
- (13 reductions, 66 cells)
- ?
-
- There are a number of other useful kinds of pattern, some of which are
- illustrated by the following examples:
-
- o Wildcard: _ matches any value at all; it is like a
- variable pattern, except that there is no
- way of referring to the matched value.
-
- o Tuples: (x,y) matches a pair whose first and second
- elements are called x and y respectively.
-
- o Lists: [x] matches a list with precisely one element
- called x.
- [_,2,_] matches a list with precisely three
- elements, the second of which is the
- integer 2.
- [] matches the empty list.
- (x:xs) matches a non-empty list with head x and
- tail xs.
-
- o As patterns: p@(x,y) matches a pair whose first and second
- components are called x and y. The
- complete pair can also be referred to
- directly as p.
-
- o (n+k) patterns: (m+1) matches an integer value greater than or
- equal to 1. The value referred to by the
- variable m is one less than the value
- matched.
-
- A further kind of pattern (called an irrefutable pattern) is introduced
- in section 9.11.
-
- Note that no variable name can be used more than once on the left hand
- side of each equation in a function definition. The following example:
-
- areTheyTheSame x x = True
- areTheyTheSame _ _ = False
-
- will not be accepted by the Gofer system, but should instead be defined
- using the notation of guards introduced in the next section:
-
- areTheyTheSame x y
- | x==y = True
- | otherwise = False
-
-
- .ST 9.2 Guarded equations
- ----------------------
- Each of the equations in a function definition may contain `guards'
- which require certain conditions on the values of the function's
- arguments to be met. As an example, here is a function which uses the
- standard prelude function even :: Int -> Bool to determine whether its
- argument is an even integer or not, and returns the string "even" or
- "odd" as appropriate:
-
- oddity n | even n = "even"
- | otherwise = "odd"
-
- In general, an equation using guards takes the form:
-
- f x1 x2 ... xn | condition1 = e1
- | condition2 = e2
- .
- .
- | conditionm = em
-
- This equation is used by evaluating each of the conditions in turn
- until one of them evaluates to "True", in which case the value of the
- function is given by the corresponding expression e on the right hand
- side of the `=' sign. In Gofer, the variable "otherwise" is defined to
- be equal to "True", so that writing "otherwise" as the condition in a
- guard means that the corresponding expression will always be used if no
- previous guard has been satisfied.
-
- [ASIDE: in the notation of [1], the above examples would be written as:
-
- oddity n = "even", if even n
- = "odd", otherwise
-
- f x1 x2 ... xn = e1, if condition1
- = e2, if condition2
- .
- .
- = em, if conditionm
-
- Translation between the two notations is relatively straightforward.]
-
-
- .ST 9.3 Local definitions
- ----------------------
- Function definitions may include local definitions for variables which
- can be used both in guards and on the right hand side of an equation.
- Consider the following function which calculates the number of distinct
- real roots for a quadratic equation of the form a*x*x + b*x + c = 0:
-
- numberOfRoots a b c | discr>0 = 2
- | discr==0 = 1
- | discr<0 = 0
- where discr = b*b - 4*a*c
-
- [ASIDE: The operator (==) is used to test whether two values are equal
- or not. You should take care not to confuse this with the single `='
- sign used in function definitions].
-
- Local definitions can also be introduced at an arbitrary point in an
- expression using an expression of the form:
-
- let <decls> in <expr>
-
- For example:
-
- ? let x = 1 + 4 in x*x + 3*x + 1
- 41
- (8 reductions, 15 cells)
- ? let p x = x*x + 3*x + 1 in p (1 + 4)
- 41
- (7 reductions, 15 cells)
- ?
-
-
- .ST 9.4 Recursion with integers
- ----------------------------
- Recursion is a particularly important and powerful technique in
- functional programming which is useful for defining functions involving
- a wide range of datatypes. In this section, we describe one particular
- application of recursion to give an alternative definition for the
- factorial function from section 5.
-
- Suppose that we wish to calculate the factorial of a given integer n.
- We can split the problem up into two special cases:
-
- o If n is zero then the value of n! is 1.
-
- o Otherwise, n! = 1 * 2 * ... * (n-1) * n = (n-1)! * n and so we
- can calculate the value of n! by calculating the value of (n-1)!
- and then multiplying it by n.
-
- This process can be expressed directly in Gofer using a conditional
- expression:
-
- fact1 n = if n==0 then 1 else n * fact1 (n-1)
-
- This definition may seem rather circular; in order to calculate the
- value of n!, we must first calculate (n-1)!, and unless n is 1, this
- requires the calculation of (n-2)! etc... However, if we start with
- some positive value for the variable n, then we will eventually reach
- the case where the value of 0! is required -- and this does not require
- any further calculation. The following diagram illustrates how 6! is
- evaluated using "fact1":
-
- fact1 6 ==> 6 * fact1 5
- ==> 6 * (5 * fact1 4)
- ==> 6 * (5 * (4 * fact1 3))
- ==> 6 * (5 * (4 * (3 * fact1 2)))
- ==> 6 * (5 * (4 * (3 * (2 * fact1 1))))
- ==> 6 * (5 * (4 * (3 * (2 * (1 * fact1 0)))))
- ==> 6 * (5 * (4 * (3 * (2 * (1 * 1)))))
- ==> 6 * (5 * (4 * (3 * (2 * 1))))
- ==> 6 * (5 * (4 * (3 * 2)))
- ==> 6 * (5 * (4 * 6))
- ==> 6 * (5 * 24)
- ==> 6 * 120
- ==> 720
-
- Incidentally, there are several other ways of writing the recursive
- definition of "fact1" above in Gofer. For example, using guards:
-
- fact2 n
- | n==0 = 1
- | otherwise = n * fact2 (n-1)
-
- or using pattern matching with an integer constant:
-
- fact3 0 = 1
- fact3 n = n * fact3 (n-1)
-
- Which of these you use is largely a matter of personal taste.
-
- Yet another style of definition uses the (n+k) patterns mentioned in
- section 9.1:
-
- fact4 0 = 1
- fact4 (n+1) = (n+1) * fact4 n
-
- which is equivalent to:
-
- fact5 n | n==0 = 1
- | n>=1 = n * fact5 (n-1)
-
- [COMMENT: Although each of the above definitions gives the same result
- as the original "fact" function for each non-negative integer, the
- functions can still be distinguished by the values obtained when they
- are applied to negative integers:
-
- o "fact (-1)" evaluates to the integer 1.
- o "fact1 (-1)" causes Gofer to enter an infinite loop, which is only
- eventually terminated when Gofer runs out of `stack space'.
- o "fact4 (-1)" causes an evaluation error and prints the
- message {fact4 (-1)} on the screen.
-
- To most people, this suggests that the definition of "fact4" is perhaps
- preferable to that of either "fact" or "fact1" as it neither gives the
- wrong answer without allowing this to be detected nor causes a
- potentially non-terminating computation.]
-
-
- .ST 9.5 Recursion with lists
- -------------------------
- The same kind of technique that can be used to define recursive
- functions with integers can also be used to define recursive functions
- on lists. As an example, suppose that we wish to define a function to
- calculate the length of a list. As the standard prelude already
- includes such a function called "length", we will call the function
- developed here "len" to avoid any conflict. Now suppose that we wish
- to find the length of a given list. There are two cases to consider:
-
- o If the list is empty then it has length 0
-
- o Otherwise, it is non-empty and can be written in the form (x:xs)
- for some element x and some list xs. Thus the original list is
- one element longer than xs, and so has length 1 + len xs.
-
- Writing these two cases out leads directly to the following definition:
-
- len [] = 0
- len (x:xs) = 1 + len xs
-
- The following diagram illustrates the way that this function can be
- used to determine the length of the list [1,2,3,4] (remember that this
- is just an abbreviation for 1 : 2 : 3 : 4 : []):
-
- len [1,2,3,4] ==> 1 + len [2,3,4]
- ==> 1 + (1 + len [3,4])
- ==> 1 + (1 + (1 + len [4]))
- ==> 1 + (1 + (1 + (1 + len [])))
- ==> 1 + (1 + (1 + (1 + 0)))
- ==> 1 + (1 + (1 + 1))
- ==> 1 + (1 + 2)
- ==> 1 + 3
- ==> 4
-
- As further examples, you might like to look at the following
- definitions which use similar ideas to define the functions product and
- map introduced in earlier sections:
-
- product [] = 1
- product (x:xs) = x * product xs
-
- map f [] = []
- map f (x:xs) = f x : map f xs
-
-
- .ST 9.6 Lazy evaluation
- --------------------
- Gofer evaluates expressions using a technique sometimes described as
- `lazy evaluation' which means that:
-
- o No expression is evaluated until its value is needed.
-
- o No shared expression is evaluated more than once; if the
- expression is ever evaluated then the result is shared between all
- those places in which it is used.
-
- The first of these ideas is illustrated by the following function:
-
- ignoreArgument x = "I didn't need to evaluate x"
-
- Since the result of the function "ignoreArgument" doesn't depend on the
- value of its argument "x", that argument will not be evaluated:
-
- ? ignoreArgument (1/0)
- I didn't need to evaluate x
- (1 reduction, 31 cells)
- ?
-
- In some situations, it is useful to be able to force Gofer to evaluate
- the argument to a function before the function is applied. This can be
- achieved using the function "strict" defined in the standard prelude;
- An expression of the form "strict f x" is evaluated by first evaluating
- the argument "x" and then applying the function "f" to the result:
-
- ? strict ignoreArgument (1/0)
- {primDivInt 1 0}
- (4 reductions, 29 cells)
- ?
-
- The second basic idea behind lazy evaluation is that no shared
- expression should be evaluated more than once. For example, the
- following two expressions can be used to calculate 3*3*3*3:
-
- ? square * square where square = 3 * 3
- 81
- (3 reductions, 9 cells)
- ? (3 * 3) * (3 * 3)
- 81
- (4 reductions, 11 cells)
- ?
-
- Notice that the first expression requires one less reduction than the
- second. Excluding the single reduction step needed to convert each
- integer into a string, the sequences of reductions that will be used in
- each case are as follows:
-
- .cc 5
- square * square where square = 3 * 3
- -- calculate the value of square by reducing 3 * 3 ==> 9
- -- and replace each occurrence of square with this result
- ==> 9 * 9
- ==> 81
-
- (3 * 3) * (3 * 3) -- evaluate first (3 * 3)
- ==> 9 * (3 * 3) -- evaluate second (3 * 3)
- ==> 9 * 9
- ==>
-
- Lazy evaluation is a very powerful feature of programming in a language
- like Gofer, and means that only the minimum amount of calculation is
- used to determine the result of an expression. The following example
- is often used to illustrate this point.
-
- Consider the task of finding the smallest element of a list of
- integers. The standard prelude includes a function "minimum" which can
- be used for this very purpose:
-
- ? minimum [100,99..1]
- 1
- (809 reductions, 1322 cells)
- ?
-
- (The expression [100,99..1] denotes the list of integers from 1 to 100
- arranged in decreasing order, as described in section 10.1).
-
- A rather different approach involves sorting the elements of the list
- into increasing order (using the function "sort" defined in the
- standard prelude) and then take the element at the head of the
- resulting list (using the standard function "head"). Of course,
- sorting the list in its entirety is likely to require significantly
- more work than the previous approach:
-
- ? sort [100,99..1]
- [1, 2, 3, 4, 5, 6, 7, 8, ... etc ..., 99, 100]
- (10712 reductions, 21519 cells)
- ?
-
- However, thanks to lazy-evaluation, calculating just the first element
- of the sorted list actually requires less work in this particular case
- than the first solution using "minimum":
-
- ? head (sort [100,99..1])
- 1
- (713 reductions, 1227 cells)
- ?
-
- Incidentally, it is probably worth pointing out that this example
- depends rather heavily on the particular algorithm used to "sort" a
- list of elements. The results are rather different if we compare the
- same two approaches used to calculate the maximum value in the list:
-
- ? maximum [100,99..1]
- 100
- (812 reductions, 1225 cells)
- ? last (sort [100,99..1])
- 100
- (10612 reductions, 20732 cells)
- ?
-
- This difference is caused by the fact that each element in the list
- produced by "sort" is only known once the values of all of the
- preceding elements are also known. Thus the complete list must be
- sorted in order to obtain the last element.
-
-
- .ST 9.7 Infinite data structures
- -----------------------------
- One particular benefit of lazy evaluation is that it makes it possible
- for functions in Gofer to manipulate `infinite' data structures.
- Obviously we cannot hope either to construct or store an infinite
- object in its entirety -- the advantage of lazy evaluation is that it
- allows us to construct infinite objects piece by piece as necessary
- (and to reuse the storage space used by parts of the object when they
- are no longer required).
-
- As a simple example, consider the following function which can be used
- to produce infinite lists of integer values:
-
- countFrom n = n : countFrom (n+1)
-
- If we evaluate the expression "countFrom 1", Gofer just prints the list
- of integer values beginning with 1 until it is interrupted. Once each
- element in the list has been printed, the storage used to hold that
- element can be reused to hold later elements in the list. Evaluating
- this expression is equivalent to using an `infinite' loop to print the
- list of integers in an imperative programming language:
-
- ? countFrom 1
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,^C{Interrupted!}
- (53 reductions, 160 cells)
- ?
-
- For practical applications, we are usually only interested in using a
- finite portion of an infinite data structure (just as loops in an
- imperative programming language are usually terminated after finitely
- many iterations). For example, using "countFrom" together with the
- function "take" defined in the standard prelude, we can repeat the
- calculation from section 4 to find the sum of the integers 1 to 10:
-
- ? sum (take 10 (countFrom 1))
- 55
- (62 reductions, 119 cells)
- ?
-
- [ASIDE: The expression "take n xs" evaluates to a list containing the
- first n elements of the list xs (or to xs itself if the list contains
- fewer than n elements). Thus "countFrom 1" generates the infinite list
- of integers, "take 10" ensures that only the first ten elements are
- calculated, and "sum" calculates the sum of those integers as before.]
-
- A particular advantage of using infinite data structures is that it
- enables us to describe an object without being tied to one particular
- application of that object. Consider the following definition for the
- infinite list of powers of two [1, 2, 4, 8, ...]:
-
- powersOfTwo = 1 : map double powersOfTwo
- where double n = 2*n
-
- This list be used in a variety of ways; using the operator (!!) defined
- in the standard prelude [xs!!n evaluates to the nth element of the list
- xs], we can define a function to find the nth power of 2 for any given
- integer n:
-
- twoToThe n = powersOfTwo !! n
-
- Alternatively, we can use the list "powersOfTwo" to define a function
- mapping lists of bits (represented by integers 0 and 1) to the
- corresponding decimal number: simply reverse the order of the digits,
- multiply each by the corresponding power of two and calculate the sum.
- Using functions from the standard prelude, this translates directly
- into the definition:
-
- binToDec ds = sum (zipWith (*) (reverse ds) powersOfTwo)
-
- For example:
-
- ? twoToThe 12
- 4096
- (15 reductions, 21 cells)
- ? binToDec [1,0,1,1,0]
- 22
- (40 reductions, 85 cells)
- ?
-
- .ST 9.8 Polymorphism
- -----------------
- Given the definition of "product" in section 9.5, it is easy to see
- that product takes a single argument which is a list of integers and
- returns a single integer value -- the product of the elements of the
- list. In other words, "product" has type [Int] -> Int. On the other
- hand, it is not immediately clear what the type of the function "map"
- should be. Clearly the first argument of "map" must be a function and
- both the second argument and the result are lists, so that the type of
- "map" must be of the form:
-
- (a -> b) -> [c] -> [d]
- \______/ \___/ \___/
- type of 1st type of 2nd type of result
- argument "f" argument "xs" "map f xs"
-
- But what can be said about the types a, b, c and d? One possibility
- would be to choose a = b = c = d = Int which would be acceptable for
- expressions such as "map fact [1,2,3,4]", but this would not be
- suitable in an expression such as "map chr [65,75,32]" because the
- "chr" function does not have type Int -> Int.
-
- Notice however that the argument type of "f" must be the same as the
- type of elements in the second argument (i.e. a = c) since "f" is
- applied to each element in that list. Similarly, the result type of
- "f" must be the same as the type of elements in the result list (i.e. b
- = d) since each element in this list is obtained as a result of
- applying the function "f" to some value. It is therefore reasonable to
- treat the "map" function as having any type of the form:
-
- (a -> b) -> [a] -> [b]
-
- The letters "a" and "b" used in this type expression represent
- arbitrary types and are called type variables. An object whose type
- includes one or more type variables can be thought of as having many
- different types and is often described as having a `polymorphic type'
- (literally: its type has `many shapes').
-
- The ability to define and use polymorphic functions in Gofer turns out
- to be very useful. Here are the types of some of the other polymorphic
- functions which have been used in previous examples which illustrate
- this point:
-
- length :: [a] -> Int
- (++) :: [a] -> [a] -> [a]
- concat :: [[a]] -> [a]
-
- Thus we can use precisely the same "length" function to determine both
- the length of a list of integers as well as finding the length of a
- string:
-
- ? length [1..10]
- 10
- (98 reductions, 138 cells)
- ? length "Hello"
- 5
- (22 reductions, 36 cells)
- ?
-
-
- .ST 9.9 Higher-order functions
- ---------------------------
- In Gofer, function values are treated in much the same way as any other
- kind of value; in particular, they can be used both as arguments to,
- and results of other functions.
-
- .cc 5
- Functions which manipulate other functions in this way are often
- described as `higher-order functions'. Consider the following example,
- taken from the standard prelude:
-
- (.) :: (b -> c) -> (a -> b) -> (a -> c)
- (f . g) x = f (g x)
-
- As indicated by the type declaration, we think of the (.) operator as a
- function taking two function arguments and returning another function
- value as its result. If f and g are functions of the appropriate
- types, then (f . g) is a function called the composition of f with g.
- Applying (f . g) to a value is equivalent to applying g to that value,
- and then applying f to the result [As described, far more eloquently,
- by the second line of the declaration above!].
-
- Many problems can often be described very elegantly as a composition of
- other functions. Consider the problem of calculating the total number
- of characters used in a list of strings. A simple recursive function
- provides one solution:
-
- countChars [] = 0
- countChars (w:ws) = length w + countChars ws
-
- ? countChars ["super","cali","fragi","listic"]
- 20
- (96 reductions, 152 cells)
- ?
-
- An alternative approach is to notice that we can calculate the total
- number of characters by first combining all of the words in the
- argument list into a single word (using concat) and then finding the
- length of that word:
-
- ? (length . concat) ["super","cali","fragi","listic"]
- 20
- (113 reductions, 211 cells)
- ?
-
- Another solution is to first find the length of each word in the list
- (using the "map" function to apply "length" to each word) and then
- calculate the sum of these individual lengths:
-
- ? (sum . map length) ["super","cali","fragi","listic"]
- 20
- (105 reductions, 172 cells)
- ?
-
-
- .ST 9.10 Variable declarations
- --------------------------
- A variable declaration is a special form of function definition,
- almost always consisting of a single equation of the form:
-
- var = rhs
-
- (i.e. a function declaration of arity 0). Whereas the values defined
- by function declarations of arity>0 are guaranteed to be functions, the
- values defined by variable declarations may or may not be functions:
-
- odd = not . even -- if an integer is not even then it must be odd
- val = sum [1..100]
-
- Note that variables defined like this at the top level of a file of
- definitions will be evaluated using lazy evaluation. The first time we
- refer to the variable "val" defined above (either directly or
- indirectly), Gofer evaluates the sum of the integers from 1 to 100 and
- overwrites the definition of "val" with this number. This calculation
- can then be avoided for each subsequent use of "val" (unless the file
- containing the definition of "val" is reloaded).
-
- ? val
- 5050
- (809 reductions, 1120 cells)
-
- ? val
- 5050
- (1 reduction, 7 cells)
-
- ?
-
- Because of this behaviour, we should probably try to avoid using
- variable declarations where the resulting value will require a lot of
- storage space. If we load a file of definitions including the line:
-
- longList = [1..10000]
-
- and then evaluate the expression "length longList" (eventually
- obtaining the expected result of 10000), then Gofer will evaluate the
- definition of "longList" and replace it with the complete list of
- integers from 1 upto 10000. Unlike other memory used during a
- calculation, it will not be possible to reuse this space for other
- calculations without reloading the file defining "longList", or loading
- other files instead.
-
-
- .ST 9.11 Pattern bindings and irrefutable patterns
- ----------------------------------------------
- Another useful way of defining variables uses `pattern bindings' which
- are equations of the form:
-
- pat = rhs
-
- where the expression on the left hand side is a pattern as described in
- section 9.1. As a simple example of pattern bindings, here is one
- possible definition for the function "head" which returns the first
- element in a list of values:
-
- head xs = x where (x:ys) = xs
-
- [The definition "head (x:_) = x" used in the standard prelude is
- slightly more efficient, but otherwise equivalent.]
-
- [ASIDE: Note that pattern bindings are treated quite differently from
- function bindings (of which the variable declarations described in the
- last section are a special case). There are two situations in which an
- ambiguity may occur; i.e. if the left hand side of an equation is a
- simple variable or an (n+k) pattern of the kind described in section
- 9.1. In both cases, these are treated as function bindings, the former
- being a variable declaration whilst the latter will be treated as a
- definition for the operator symbol (+).]
-
- Pattern bindings are often useful for defining functions which we might
- think of as `returning more than one value' -- although they are
- actually packaged up in a single value such as a tuple. As an example,
- consider the function "span" defined in the standard prelude.
-
- span :: (a -> Bool) -> [a] -> ([a],[a])
-
- If xs is a list of values and p is a predicate, then span p xs returns
- the pair of lists (ys,zs) such that ys++zs == xs, all of the elements
- in ys satisfy the predicate p and the first element of zs does not
- satisfy p. A suitable definition, using a pattern binding to obtain
- the two lists resulting from the recursive call to "span" is as
- follows:
-
- span p [] = ([],[])
- span p xs@(x:xs')
- | p x = let (ys,zs) = span p xs' in (x:ys,zs)
- | otherwise = ([],xs)
-
-
- For consistency with the lazy evaluation strategy used in Gofer, the
- right hand side of a pattern binding is not evaluated until the value
- of one of the variables bound by that pattern is required. The
- definition:
-
- (0:xs) = [1,2,3]
-
- will not cause any errors when it is loaded into Gofer, but will cause
- an error if we attempt to evaluate the variable xs:
-
- ? xs
- {v120 [1, 2, 3]}
- (11 reductions, 46 cells)
- ?
-
- The variable name "v120" appearing in this expression is the name of a
- function called a `conformality check' which is defined automatically
- by Gofer to ensure that the value on the right hand side of the pattern
- binding conforms with the pattern on the left.
-
- Compare this with the behaviour of pattern matching in function
- definitions such as:
-
- ? example [1] where example (0:xs) = "Hello"
- {v126 [1]}
- (4 reductions, 22 cells)
- ?
-
- where the equivalent of the conformality check is carried out
- immediately even if none of the values of the variables in the pattern
- are actually required. The reason for this difference is that the
- arguments supplied to a function must be evaluated to determine which
- equation in the definition of the function should be used. The error
- produced by the example above was caused by the fact that the argument
- [1] does not match the pattern used in the equation defining "example"
- (represented by an internal Gofer function called "v126").
-
- A different kind of behaviour can be obtained using a pattern of the
- form ~pat, known as an irrefutable (or lazy) pattern. This pattern can
- initially be matched against any value, delaying the check that this
- value does indeed match pat until the value of one of the variables
- appearing in it is required. The basic idea (together with the method
- used to implement irrefutable patterns in Gofer) is illustrated by the
- identity:
-
- f ~pat = rhs is equivalent to f v = rhs where pat=v
-
- The following examples, based very closely on those given in the
- Haskell report [5], illustrate the use of irrefutable patterns. The
- variable "undefined" used in these examples is included in the standard
- prelude and causes a run-time error each time it is evaluated
- (technically speaking, it represents the bottom element of the relevant
- semantic domain, and is the only value having all possible types):
-
- (\ (x,y) -> 0) undefined = {undefined}
- (\~(x,y) -> 0) undefined = 0
-
- (\ [x] -> 0) [] = {v113 []}
- (\~[x] -> 0) [] = 0
-
- (\~[x, (a,b)] -> x) [(0,1),undefined] = {undefined}
- (\~[x,~(a,b)] -> x) [(0,1),undefined] = (0,1)
-
- (\ (x:xs) -> x:x:xs) undefined = {undefined}
- (\~(x:xs) -> x:x:xs) undefined = {undefined}:{undefined}:{undefined}
-
- Irrefutable patterns are not used very frequently, although they are
- particularly convenient in some situations (see section 12 for some
- examples). Be careful not to use irrefutable patterns where they are
- not appropriate. An attempt to define a map function "map'" using:
-
- map' f ~(x:xs) = f x : map' f xs
- map' f [] = []
-
- turns out to be equivalent to the definition:
-
- map' f ys = f x : map f xs where (x:xs) = ys
-
- and will not behave as you might have intended:
-
- ? map' ord "abc"
- [97, 98, 99, {v124 []}, {v124 []}, {v^C{Interrupted!}
- (35 reductions, 159 cells)
- ?
-
-
- .ST 9.12 Type declarations
- -----------------------
- The type system used in Gofer is sufficiently powerful to enable Gofer
- to determine the type of any function without the need to declare the
- types of its arguments and the return value as in some programming
- languages. Despite this, Gofer allows the use of type declarations of
- the form:
-
- var1, ..., varn :: type
-
- which enable the programmer to declare the intended types of the
- variables var1, ..., varn defined in either function or pattern
- bindings. There are a number of benefits of including type
- declarations of this kind in a program:
-
- o Documentation: The type of a function often provides useful
- information about the way in which a function is to be used --
- including the number and order of its arguments.
-
- o Restriction: In some situations, the type of a function inferred
- by Gofer is more general than is required. As an example,
- consider the following function, intended to act as the identity
- on integer values:
-
- idInt x = x
-
- Without an explicit type declaration, Gofer treats "idInt" as a
- polymorphic function of type a -> a and the expression "idInt 'A'"
- does not cause a type error. This problem can be solved by using
- an explicit type declaration to restrict the type of "idInt" to a
- particular instance of the polymorphic type a -> a:
-
- idInt :: Int -> Int
-
- Note that a declaration such as:
-
- idInt :: Int -> a
-
- is not a valid type for the function "idInt" (the value of the
- expression "idInt 42" is an integer and cannot be treated as
- having an arbitrary type, depending on the value of the type
- variable "a"), and hence will not be accepted by Gofer.
-
- o Consistency check: As illustrated above, declared types are always
- checked against the definition of a value to make sure that they
- are compatible. Thus Gofer can be used to check that the
- programmer's intentions (as described by the types assigned to
- variables in type declarations) are consistent with the
- definitions of those values.
-
- o Overloading: Explicit type declarations can be used to solve a
- number of problems associated with overloaded functions and
- values. See section 14 for further details.
- .pa
- .co-------------------------------------------------------------------|
- .>ch10
- .ST 10. INCREASING YOUR POWER OF EXPRESSION
-
- This section describes a number of useful extensions to the basic range
- of expressions used in the previous sections. None of these add any
- extra computational power to Gofer -- anything that can be done with
- these constructs could also be done with the constructs already
- described. They are however included in Gofer because they allow many
- expressions and function definitions to be written more clearly and
- concisely than the equivalent expressions without these notations.
-
- .ST 10.1 Arithmetic sequences
- -------------------------
- A number of useful lists can be generated using the notation of
- arithmetic sequences (so named because of their similarity to
- arithmetic progressions in mathematics). The following list summarises
- the four forms of sequence expression that can be used in Gofer,
- together with their translation using the standard functions enumFrom,
- enumFromTo, enumFromThen and enumFromThenTo:
-
- [ n .. ] enumFrom n
-
- Produces the (potentially infinite) list of values
- starting with the value of n and increasing in
- single steps.
-
- e.g. [1..] = [1, 2, 3, 4, 5, 6, 7, 8, 9, etc...
-
- [ n .. m ] enumFromTo n m
-
- Produces the list of elements from n upto and
- including m in single steps. If m is less than n
- then the list is empty.
-
- e.g. [-3..3] = [-3, -2, -1, 0, 1, 2, 3]
- [1..1] = [1]
- [9..0] = []
-
- [ n, m .. ] enumFromThen n m
-
- Produces the (potentially infinite) list of values
- whose first two elements are given by the values n
- and m. If m is greater than n then the following
- elements of the list are increasing in steps of
- the same size. A similar result is obtained if m
- is less than n in which case the elements of
- [n,m..] will be decreasing. If n and m are equal
- then [n,m..] is an infinite list in which each
- element is equal to n.
-
- e.g. [1,3..] = [1, 3, 5, 7, 9, 11, 13, etc...
- [0,0..] = [0, 0, 0, 0, 0, 0, 0, etc...
- [5,4..] = [5, 4, 3, 2, 1, 0, -1, etc...
-
- [ n, n' .. m ] enumFromThenTo n n' m
-
- Produces the list of elements from [n,n'..] upto
- the limit value m. If m is less than n and
- [n,n'..] is increasing, or m is greater than n and
- [n,n'..] is decreasing the resulting list will be
- empty.
-
- e.g. [1,3..12] = [1, 3, 5, 7, 9, 11]
- [0,0..10] = [0, 0, 0, 0, 0, 0, 0, etc...
- [5,4..1] = [5, 4, 3, 2, 1]
-
- In the standard prelude, the functions enumFrom, enumFromTo,
- enumFromThen and enumFromThenTo are overloaded and may also be used to
- enumerate lists of characters or floating point values:
-
- ? ['0'..'9'] ++ ['A'..'Z']
- 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
- (397 reductions, 542 cells)
-
- ? [1.2, 1.35 .. 2.00]
- [1.2, 1.35, 1.5, 1.65, 1.8, 1.95]
- (56 reductions, 133 cells)
-
- ?
-
- Arithmetic sequences such as those described above play the same role
- in functional programming languages as the iterative `for' constructs
- in traditional imperative languages. A good example of this is the
- example in section 4 used to calculate the sum of the integers from 1
- upto 10 -- "sum [1..10]". An equivalent program in an imperative
- language might look something like (especially if you think of C!):
-
- int i;
- int total=0;
- for (i=1; i<=10; i++)
- total = total + i;
- return total;
-
- The advantages of the functional notation in this case are clear:
-
- o It is more compact.
-
- o It separates the task of generating the sequence of integers
- [1..10] from the task of finding their sum.
-
- o It does not require the declaration or use of auxiliary variables
- such as "i" and "total" in the above.
-
-
- .ST 10.2 List comprehensions
- -------------------------
- List comprehensions provide another very powerful and compact notation
- for describing certain kinds of list expression. The basic form of a
- list comprehension is:
-
- [ <expr> | <qualifiers> ]
-
- There are three kinds of qualifier that can be used in Gofer:
-
- o Generators: A qualifier of the form pat<-exp is used to extract
- each element that matches the pattern pat from the list exp in the
- order that they elements appear in that list. A simple example of
- this is the expression [x*x | x<-[1..10]] which denotes the list
- of the squares of the integers between 1 and 10 inclusive and
- evaluates to [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] as expected.
-
- Formally, we can define the meaning of a list comprehension with a
- single generator by the equation:
-
- [ e | pat <- exp ] = loop exp
- where loop [] = []
- loop (pat:xs) = e : loop xs
- loop (_:xs) = loop xs
-
- If pat is an irrefutable pattern (for example, a variable) then
- this is equivalent to:
-
- [ e | pat <- exp ] = map f exp
- where f pat = e
-
- The full definition is needed for those cases where the pattern
- pat may not match all of the elements in the list exp. This is
- the case in expressions such as [ y | (3,y)<-[(1,2),(3,4),(5,6)] ]
- which evaluates to the singleton list [4].
-
- o Filters: A boolean valued expression may also be used as a
- qualifier in which case it is often called a filter. We can
- define the meaning of a list comprehension with a single filter by
- the equation:
-
- [ e | condition ] = if condition then [e] else []
-
- Whilst this form of list comprehension is occasionally useful as
- it stands, it is more common to use filters in conjunction with
- generators as described below.
-
- o Local definitions: A qualifier of the form pat=expr can be used to
- introduce a local definition within a list comprehension. Its
- meaning can be defined formally using the equation:
-
- [ e | pat = exp ] = [ let pat=exp in e ]
-
- As in the case of filters, local definitions are more commonly
- used within lists of more than one qualifier as described below.
- Particular care should be taken to distinguish a filter of the
- form pat==expr from a local definition of the form pat=expr.
-
- [ASIDE: I originally suggested this form of qualifier in a message
- sent to the Haskell mailing list, only to discover that a similar
- (and more comprehensive) suggestion had been made by Kevin Hammond
- almost a year earlier. There was a certain amount of controversy
- surrounding the choice of an appropriate syntax and semantics for
- the construct and consequently, this feature is not currently part
- of the Haskell standard. The syntax and semantics above is
- implemented by Gofer in the hope that it will give functional
- programmers an opportunity to experiment with this facility in
- their own programs.]
-
- The real power of this notation is that it is possible to use several
- qualifiers, separated by commas on the right of the vertical bar `|'
- symbol in a list comprehension. Formally, if qs1 and qs2 are two such
- lists of qualifiers, then we can define the meaning of multiple
- qualifiers using:
-
- [ e | qs1, qs2 ] = concat [ [ e | qs2 ] | qs1 ]
-
- The following examples illustrate how this definition works in
- practice:
-
- o Variables generated by later qualifiers vary more quickly than
- those generated by earlier qualifiers:
-
- ? [ (x,y) | x<-[1..3], y<-[1..2] ]
- [(1,1), (1,2), (2,1), (2,2), (3,1), (3,2)]
- (107 reductions, 246 cells)
- ?
-
- o Later qualifiers may use the values generated by earlier ones:
-
- ? [ (x,y) | x<-[1..3], y<-[1..x]]
- [(1,1), (2,1), (2,2), (3,1), (3,2), (3,3)]
- (107 reductions, 246 cells)
-
- ? [ x | x<-[1..10], even x ]
- [2, 4, 6, 8, 10]
- (108 reductions, 171 cells)
- ?
-
- o Variables defined in later qualifiers hide those introduced by
- earlier ones. The following expressions are valid list
- comprehensions, but this style of definition in which names are
- reused can result in programs which are difficult to understand,
- and is not recommended:
-
- ? [ x | x<-[[1,2],[3,4]], x<-x ]
- [1, 2, 3, 4]
- (18 reductions, 53 cells)
-
- ? [ x | x<-[1,2], x<-[3,4] ]
- [3, 4, 3, 4]
- (18 reductions, 53 cells)
- ?
-
- o Changing the order of qualifiers has a direct effect on
- efficiency. The following two examples produce the same result,
- but the first uses more reductions and cells because it repeats
- the evaluation of "even x" for each possible value of "y".
-
- ? [ (x,y) | x<-[1..3], y<-[1..2], even x ]
- [(2,1), (2,2)]
- (110 reductions, 186 cells)
-
- ? [ (x,y) | x<-[1..3], even x, y<-[1..2] ]
- [(2,1), (2,2)]
- (62 reductions, 118 cells)
- ?
-
- The following example illustrates a similar kind of behaviour with
- local definitions; in the first case the expression "fact x" is
- evaluated twice for each possible value of "x", whilst the second
- expression uses a local definition to ensure that the evaluation
- is not repeated:
-
- ? [ fact x + y | x<-[1..3], y<-[1..2] ]
- [2, 3, 3, 4, 7, 8]
- (246 reductions, 398 cells)
-
- ? [ factx + y | x<-[1..3], factx = fact x, y<-[1..2] ]
- [2, 3, 3, 4, 7, 8]
- (173 reductions, 294 cells)
- ?
-
-
- .ST 10.3 Lambda expressions
- ------------------------
- In addition to named function definitions, Gofer also allows the
- definition and use of unnamed functions using a `lambda expression' of
- the form:
-
- \ <atomic patterns> -> <expr>
-
- [ASIDE: This is a slight generalisation of the form of lambda
- expression used in most theoretical treatments of functional
- programming and dating back to the pioneering work of logicians
- including Alonzo Church and Haskell Curry, from whom the programming
- language takes its name. The `\' character used at the beginning of a
- Gofer lambda expression has been chosen for its resemblance to the
- greek letter lambda that might be used if the standard character set
- were a little larger.]
-
- This expression denotes a function taking a number of parameters (one
- for each pattern) and producing the result specified by the expression
- to the right of the -> symbol. For example, (\x->x*x) represents the
- function which takes a single integer argument `x' and produces the
- square of that number as its result. Another example is the lambda
- expression (\x y->x+y) which takes two integer arguments and outputs
- their sum; this expression is in fact equivalent to the (+) operator:
-
- ? (\x y->x+y) 2 3
- 5
- (3 reductions, 7 cells)
- ?
-
- A lambda expression of the form illustrated above is equivalent to the
- following expression using a local definition:
-
- (let newName <atomic patterns> = <expr> in newName)
-
- where "newName" is a new variable name, chosen to avoid conflicts with
- other variables that are already in use. This name will be printed if
- you enter an expression involving a lambda expression without supplying
- the full number of parameters for that function:
-
- ? (\x y -> x+y) 42
- v117 42
- (2 reductions, 14 cells)
- ?
-
- Lambda expressions are particularly useful for certain styles of
- functional programming; an example of this is the continuation-based
- approach to I/O described in section 12.
-
-
- .ST 10.4 Case expressions
- ---------------------
- A case expression can be used to evaluate an expression and, depending
- on the result, return one of a number of possible values. As such,
- case statements are a straightforward generalisation of conditional
- expressions. Indeed, an expression of the form "if e then t else f" is
- in fact equivalent to the case expression:
-
- case e of
- True -> t
- False -> f
-
- In general, a case expression takes the form "case exp of alts" where
- exp is the expression to be evaluated and alts is a list of
- alternatives, each of which is of the form:
-
- pat -> rhs for a simple alternative
-
- or: pat | condition1 -> rhs1 using guard expressions as
- | condition2 -> rhs2 described in section 9.2 for
- . function definitions
- .
- | conditionn -> rhsn
-
- In Gofer, a case expression of the form case e of alts is implemented
- by choosing a new function name "newName" as in the previous section
- and using the alternatives in alts to construct an appropriate
- definition for this function (essentially by replacing each `->' symbol
- with a `=' symbol). The complete case expression is then treated as
- being equivalent to the expression "newName e". A simple example of
- this is the "scanl" function whose definition in the standard prelude:
-
- scanl f q xs = q : (case xs of
- [] -> []
- x:xs -> scanl f (f q x) xs)
-
- is equivalent to:
-
- scanl f q xs = q : scanl' xs
- where scanl' [] = []
- scanl' (x:xs) = scanl f (f q x) xs
-
- This latter form is precisely the definition used in [1] (but using the
- name "scan" where Gofer uses "scanl").
-
- Evaluating a case expression in which none of the alternatives match
- the value of the discriminant results in an error such as the
- following:
-
- ? case [1,2] of [] -> "empty list"
- {v117 [1, 2]}
- (6 reductions, 31 cells)
- ?
-
- The function name "v117" which appears here is the name of the function
- which is used internally by Gofer to implement the case expression
- whilst the expression "[1, 2]" gives the discriminant value which could
- not be matched.
-
- By combining case expressions with the lambda expressions introduced in
- the previous section, any function declaration can be translated into a
- single equation of the form <functionName> = <expr>. For example, the
- standard function "map" whose definition is usually written as:
-
- map f [] = []
- map f (x:xs) = f x : map f xs
-
- can also be defined by the equation:
-
- map = \f xs -> case xs of
- [] -> []
- (y:ys) -> f y : map f ys
-
- This kind of translation is used in the implementation of many
- functional programming languages, including Gofer. See Simon Peyton
- Jones book [2] for more details of this.
-
-
- .ST 10.5 Operator sections
- ----------------------
- As we have seen, most functions in Gofer taking more than one argument
- are treated as a function of a single argument, whose result is a
- function which can then be applied to the remaining arguments. Thus
- "(+) 1" denotes the function which takes an integer argument "n" and
- returns the integer value "1+n". Functions of this kind involving
- operator symbols are sufficiently common that Gofer provides a special
- syntax for them. Using e to denote an atomic expression and the symbol
- "*" to represent an arbitrary infix operator, there are functions (e *)
- and (* e), known as `sections of the operator (*)' defined by:
-
- (e *) x = e * x
- (* e) x = x * e
-
- or, using lambda expressions as introduced in section 10.3:
-
- (e *) = \x -> e * x
- (* e) = \x -> x * e
-
- For example: (1+) is the successor function which returns the value
- of its argument plus 1,
- (1.0/) is the reciprocal function,
- (/2) is the halving function,
- (:[]) is the function which maps any value to the
- singleton list containing that element.
-
- In Gofer, the expressions "(e *)" and "(* e)" are actually treated as
- abbreviations for "(*) e" and "flip (*) e" respectively, where "flip"
- is the function defined by:
-
- flip :: (a -> b -> c) -> b -> a -> c
- flip f x y = f y x
-
- There is an important special case which occurs with an expression of
- the form (- e); this is interpreted as "negate e" and not as the
- section which subtracts the value of "e" from its argument. The latter
- function can be written as the section (+ (- e)) or as "subtract e"
- where "subtract" is the function defined in the standard prelude using:
-
- subtract = flip (-)
-
-
- .ST 10.6 Explicitly typed expressions
- ----------------------------------
- As described in section 9.12, it is often useful to be able to declare
- the type of a variable defined in a function or pattern binding. For
- much the same reasons, Gofer allows expressions of the form:
-
- <expr> :: <type>
-
- so that the type of an expression can be specified explicitly. Note
- that the :t command can be used to find the type of a particular
- expression that is inferred by Gofer:
-
- ? :t \x -> [x]
- \x -> [x] :: a -> [a]
-
- ? :t sum . map length
- sum . map length :: [[a]] -> Int
-
- ?
-
- The types inferred in each case can be modified by including explicit
- types in these expressions:
-
- ? :t (\x -> [x]) :: Char -> String
- \x -> [x] :: Char -> String
-
- ? :t sum . map (length :: String -> Int)
- sum . map length :: [String] -> Int
-
- ?
-
- Note that an error occurs if the type declared in an explicitly typed
- expression is not compatible with the type inferred by Gofer:
-
- ? :t (\x -> [x]) :: Int -> a
- ERROR: Declared type too general
- *** Expression : \x -> [x]
- *** Declared type : Int -> a
- *** Inferred type : Int -> [Int]
-
- ?
-
- Explicitly typed expressions are most commonly used together with
- overloaded functions and values as described in section 14.
- .pa
- .co-------------------------------------------------------------------|
- .>ch11
- .ST 11. USER-DEFINED DATATYPES AND TYPE SYNONYMS
-
- .ST 11.1 Datatype definitions
- -------------------------
- In addition to the wide range of built-in datatypes described in
- section 7, Gofer also allows the definition of new datatypes using
- declarations of the form:
-
- data DatatypeName a1 ... an = constr1 | ... | constrm
-
- where DatatypeName is the name of a new type constructor of arity n>=0,
- a1, ..., an are distinct type variables representing the arguments of
- DatatypeName and constr1, ..., constrm (m>=1) describe the way in which
- elements of the new datatype are constructed. Each constr can take one
- of two forms:
-
- o Name type1 ... typer where Name is a previously unused constructor
- function name (i.e. an identifier beginning with a capital
- letter). This declaration introduces Name as a new constructor
- function of type: type1 -> ...-> typer -> DatatypeName a1 ... an.
-
- o type1 CONOP type2 where CONOP is a previously unused constructor
- function operator (i.e. an operator symbol beginning with a
- colon). This declaration introduces (CONOP) as a new constructor
- function of type: type1 -> type2 -> DatatypeName a1 ... an.
-
- [N.B. only the type variables a1, ..., an may appear in the type
- expressions in each constr in the definition of DatatypeName.]
-
-
- As a simple example, the following definition introduces a new type Day
- with elements Sun, Mon, Tue, Wed, Thu, Fri and Sat:
-
- data Day = Sun | Mon | Tue | Wed | Thu | Fri | Sat
-
- Simple functions manipulating elements of type Day can be defined using
- pattern matching:
-
- what_shall_I_do Sun = "relax"
- what_shall_I_do Sat = "go shopping"
- what_shall_I_do _ = "looks like I'll have to go to work"
-
- Another example uses a pair of constructors to provide a representation
- for temperatures which may be given using either of the centigrade or
- fahrenheit scales:
-
- data Temp = Centigrade Float | Fahrenheit Float
-
- freezing :: Temp -> Bool
- freezing (Centigrade temp) = temp <= 0.0
- freezing (Fahrenheit temp) = temp <= 32.0
-
- The following example uses a type variable on the left hand side of the
- datatype definition to implement a Set type constructor for
- representing sets using a list of values:
-
- data Set a = Set [a]
-
- For example, Set [1,2,3] is an element of type Set Int, representing
- the set of integers {1, 2, 3} whilst Set ['a'] represents a singleton
- set of type Set Char. As this example shows, it is possible to use the
- same name simultaneously as both a type constructor and as a
- constructor function.
-
- Datatype definitions may also be recursive, using the name of the
- datatype being defined on the right hand side of the datatype
- definition (mutually recursive datatype definitions are also
- permitted). The following example is taken from the Haskell report [5]
- and defines a type representing binary trees with values of a
- particular type at their leaves:
-
- data Tree a = Lf a | Tree a :^: Tree a
-
- For example, (Lf 12 :^: (Lf 23 :^: Lf 13)) :^: Lf 10 has type Tree Int
- and represents the binary tree:
-
- ,--- 12
- ,--|
- | | ,--- 23
- | `--|
- | `--- 13
- --|
- `--- 10
-
- As an example of a function defined on trees, here are two definitions
- using recursion and pattern matching on tree valued expressions which
- calculate the list of elements at the leaves of a tree traversing the
- branches of the tree from left to right. The first definition uses a
- simple definition, whilst the second uses an `accumulating parameter'
- giving a more efficient algorithm:
-
- leaves, leaves' :: Tree a -> [a]
-
- leaves (Lf l) = [l]
- leaves (l:^:r) = leaves l ++ leaves r
-
- leaves' t = leavesAcc t []
- where leavesAcc (Lf l) = (l:)
- leavesAcc (l:^:r) = leavesAcc l . leavesAcc r
-
- Using the binary tree above as an example:
-
- ? leaves ((Lf 12 :^: (Lf 23 :^: Lf 13)) :^: Lf 10)
- [12, 23, 13, 10]
- (24 reductions, 73 cells)
- ? leaves' ((Lf 12 :^: (Lf 23 :^: Lf 13)) :^: Lf 10)
- [12, 23, 13, 10]
- (20 reductions, 58 cells)
- ?
-
-
- .ST 11.2 Type synonyms
- ------------------
- Type synonyms are used to provide convenient abbreviations for type
- expressions. A type synonym is introduced by a declaration of the
- form:
-
- type Name a1 ... an = expansion
-
- where Name is the name of a new type constructor of arity n>=0, a1,
- ..., an are distinct type variables representing the arguments of Name
- and expansion is a type expression. Note that the only type variables
- permitted in the expansion type are those on the left hand side of the
- synonym definition. Using this declaration any type expression of the
- form:
-
- Name type1 ... typen
-
- is treated as an abbreviation of the type expression obtained from
- expansion by replacing each of the type variables a1, ..., an with the
- corresponding type type1, ..., typen.
-
- The most frequently used type synonym is almost certainly the String
- type which is a synonym for [Char]:
-
- type String = [Char]
-
- [ASIDE: This definition is actually built in to the Gofer system, but
- the effect is the same as if this declaration were included in the
- standard prelude.]
-
- Note that the types of expressions inferred by Gofer will not usually
- contain any type synonyms unless an explicit type signature is given,
- either using an explicitly typed expression (section 10.6) or a type
- declaration (section 9.12):
-
- ? :t ['c']
- ['c'] :: [Char]
- ? :t ['c'] :: String
- ['c'] :: String
- ?
-
- Unlike the datatype declarations described in the previous section,
- recursive (and mutually recursive) synonym declarations are not
- permitted. This rules out examples such as:
-
- type BadSynonym = [BadSynonym]
-
- and ensures that the process of expanding all of the type synonyms used
- in any particular type expression will always terminate. The same
- property does not hold for the illegal definition above, in which any
- attempt to expand the type BadSynonym would lead to the non-terminating
- sequence:
-
- BadSynonym ==> [BadSynonym] ==> [[BadSynonym]] ==> ....
- .pa
- .co-------------------------------------------------------------------|
- .>ch12
- .ST 12. DIALOGUES: INPUT AND OUTPUT
-
- The Gofer system implements a subset of the facilities for programs
- involving I/O described in the Haskell report [5]. In particular, this
- makes it possible for Gofer programs to be run interactively, and to
- make limited use of text files for both reading and writing. A
- significant factor in the design of the Haskell I/O facilities is that
- it allows the use of such programs without loss of referential
- transparency.
-
- .ST 12.1 Basic description
- ----------------------
- Programs using the I/O facilities in Gofer are modelled by functions of
- type Dialogue, defined by the type synonym:
-
- type Dialogue = [Response] -> [Request]
-
- In other words, a Gofer program produces a list of output values, each
- of which may be thought of as a request for some particular input or
- output action, and obtains the corresponding list of operating system
- responses as its input. Note that the input list of responses will be
- evaluated lazily; i.e. we can ensure that we do not attempt to obtain
- the response to a given request until that request has been completed.
-
- The current range of requests supported by Gofer is described by the
- following datatype definition, taken from the standard prelude:
-
- data Request = -- file system requests:
- ReadFile String
- | WriteFile String String
- | AppendFile String String
- -- channel system requests:
- | ReadChan String
- | AppendChan String String
- -- environment requests:
- | Echo Bool
-
- Each response is an element of the type defined by the following
- datatype definition, using an auxiliary datatype IOError to describe a
- variety of error conditions that may occur:
-
- data Response = Success
- | Str String
- | Failure IOError
-
- data IOError = WriteError String
- | ReadError String
- | SearchError String
- | FormatError String
- | OtherError String
-
- The following list describes the kind of I/O behaviour specified by
- each form of Request and indicates the possible Response values that
- may be obtained in each case:
-
- o ReadFile string: Read contents of file named by "string".
- Possible responses to this request are:
-
- o Str contents if the request is successful, where "contents"
- is a string (evaluated lazily) containing the contents of the
- file specified by the ReadFile request.
-
- o Failure (SearchError name) occurs if file "name" cannot be
- accessed.
-
- o Failure (ReadError name) occurs if some other error occurs
- whilst opening the file "name".
-
- o WriteFile name string: Write the given "string" to the file
- "name". If the file does not already exist, it is created before
- attempting to write the value to file. If the file already exists
- then it will be truncated to zero length before the write begins.
- No response is obtained until the string argument has been fully
- evaluated and its contents written to file. Possible responses
- are:
-
- o Success if the write to file was completed successfully.
-
- o Failure (WriteError msg) if an error was detected whilst
- trying to perform the output. If the problem occurred whilst
- attempting to open the specified file, then "msg" contains
- the filename, otherwise it contains a printable
- representation of the evaluation error which occurred.
-
- o AppendFile name string: Similar to the WriteFile request except
- that the value of the given "string" is appended onto the file
- "name" if that file already exists. The responses that may be
- obtained from this request are the same as those for WriteFile.
-
- o ReadChan name: Read from the input stream "name". Note that
- it is an error to attempt to read from the same channel more than
- once in the same program. Possible responses are:
-
- o Str contents if the request is successful, where "contents"
- is a string (evaluated lazily) containing the list of
- characters entered on the input stream.
-
- o Failure (SearchError name) if the named channel cannot be
- found. The only input channel known to Gofer is the standard
- input channel "stdin". For convenience, the standard prelude
- defines the variable stdin bound to this string.
-
- o Failure (ReadError name) if a ReadChan request for the named
- channel has already been given by a previous request.
-
- o AppendChan name string: Output "string" on channel "name". No
- response is obtained until the string has been fully evaluated and
- written to the named channel. Possible responses are:
-
- o Success if the append to channel was completed successfully.
-
- o Failure (SearchError name) if the named channel cannot be
- found. The only output channels known to Gofer are "stdout",
- "stderr" and "stdecho" (which is actually just another name
- for "stdout" in Gofer). For convenience, the standard
- prelude defines variables stdout, stderr and stdecho bound to
- the corresponding string values.
-
- o Failure (WriteError msg) if an error is detected whilst
- trying to perform the output. The string "msg" contains a
- printable representation of the evaluation error which
- occurred.
-
- o Echo status: Set the echo status on the standard input channel
- stdin to the given boolean value. If the echo status is True,
- then user input will be echoed onto the screen as it is typed and
- the usual line editing facilities (such a backspace or delete)
- provided by the host system can be used to edit the input lines as
- they are entered. If the echo status is False, then individual
- characters may be read from the standard input channel without any
- echo or line editing features.
-
- Note that at most one Echo request can be used in a program, and
- must precede any ReadChan request for stdin. If not set by an
- explicit Echo request, the echo status defaults to True. Possible
- responses are:
-
- o Success if the request was completed successfully.
-
- o Failure (OtherError msg) if the request could not be
- completed either because a readChannel request for stdin has
- already been processed, or because a previous Echo request
- has already been given. The corresponding values of "msg"
- are "stdin already in use" and "repeated Echo request"
- respectively.
-
- A simple example of a program using these facilities to output a short
- message on the standard output stream is:
-
- helloWorld :: Dialogue
- helloWorld resps = [AppendChan stdout "hello, world"]
-
- Any expression entered into Gofer of type "Dialogue" will be treated as
- a Gofer program using I/O and will be executed accordingly:
-
- ? helloWorld
- hello, world
- (1 reduction, 28 cells)
- ?
-
- Notice that without the explicit type declaration, the type that would
- be inferred for helloWorld would be a -> [Request], and hence
- helloWorld would not be executed as a Dialogue program. This point can
- be illustrated using lambda expressions:
-
- ? \resps -> [AppendChan stdout "hello, world"]
- v128
- (1 reduction, 7 cells)
-
- ? (\resps -> [AppendChan stdout "hello, world"]) :: Dialogue
- hello, world
-
- (1 reduction, 28 cells)
- ?
-
- In many cases the structure of an expression is enough to fully
- determine its type as Dialogue (or equivalently as [Response] ->
- [Request]), in which case no explicit types are required to ensure that
- the expression is treated as a Gofer program using I/O:
-
- ? \~[Success] -> [AppendChan stdout "hello, world"]
- hello, world
- (1 reduction, 29 cells)
- ?
-
- Note the use of the irrefutable pattern ~[Success] for the lambda
- expression in the last example; without this, the usual rules of
- pattern matching as described in section 9 would force Gofer to try to
- match the pattern [Success] against the list of responses, before the
- corresponding request had been produced:
-
- ? \ [Success] -> [AppendChan stdout "hello, world"]
-
- Aborting Dialogue:
- {error "Attempt to read response before request complete"}
- (50 reductions, 229 cells)
- ?
-
- The next example takes a single string as a parameter and displays the
- contents of the corresponding file:
-
- showFile :: String -> Dialogue
- showFile name ~(read:_) = [ReadFile name, AppendChan stdout result]
- where result = case read of Str contents -> contents
- Failure _ -> "Can't open " ++ name
-
- With a few modifications, we can implement a similar program which
- prompts for, and reads, a filename from the standard input and then
- reads and displays the contents of that file as before. This program
- is based on a similar example in the Haskell report [5]:
-
- main ~(Success : ~(Str userInput : ~(r3 : _)))
- = [ AppendChan stdout "Please type a filename: ",
- ReadChan stdin,
- ReadFile name,
- AppendChan stdout (case r3 of Str contents -> contents
- Failure _ -> "Can't open "
- ++ name)
- ] where (name : _) = lines userInput
-
-
- .ST 12.2 Continuation style I/O
- ---------------------------
- As an alternative to the `stream-based' approach to programs using the
- I/O facilities in Gofer, the standard prelude defines a family of
- functions which enables such programs to be written in a `continuation'
- style. The basic idea is to define a function corresponding to each
- different kind of request, whose parameters include the values required
- to make the request together with two continuations. The continuations
- are functions describing "what to do next", one of which is used if the
- request is successful, the other if the request fails.
-
- As an example, the ReadFile request is represented by the function
- "readFile" whose definition is equivalent to:
-
- readFile name fail succ ~(r:rs) = ReadFile name : rest rs
- where rest = case r of Str s -> succ s
- Failure ioerror -> fail ioerror
-
- The first thing to happen when a dialogue expression of the form
- "readFile name fail succ" is evaluated is that the corresponding
- request "ReadFile name" is added to the list of I/O requests. A new
- dialogue value "rest" is chosen, depending on the response to the
- ReadFile request, and the program continues by passing the remaining
- part of the response list to "rest". The functions "succ" and "fail"
- (called the success and failure continuations respectively) describe
- the way in which the new dialogue "rest" is obtained.
-
- The following example (edited a little to fit within the margins of this
- document) shows how the readFile function described above can be used to
- print the contents of a file called "test" on the display:
-
- ? readFile "test" (\ioerror resps -> [])
- (\s resps->[AppendChan stdout s])
- This is a test message
-
- (4 reductions, 52 cells)
- ?
-
- The success continuation "(\s resps->[AppendChan stdout s])" used here
- receives the contents of the file "test" in the the parameter "s" and
- uses an AppendChan request to output that string on the display. As
- this example shows, the stream based approach of the previous section
- can be combined with the continuation based style of I/O without any
- difficulty. The failure continuation "(\ioerror resps -> [])" ignores
- the error condition "ioerror" which caused the request to fail and
- gives a dialogue which terminates immediately without any action. For
- example, assuming that the file "Test" cannot be found:
-
- ? readFile "Test" (\ioerror resps -> [])
- (\s resps->[AppendChan stdout s])
-
- (4 reductions, 24 cells)
- ?
-
- In practice, it is usually a good idea to produce some kind of
- diagnostic message when an error occurs:
-
- ? readFile "Test"
- (\ioerror resps -> [AppendChan stdout (show' ioerror)])
- (\s resps -> [AppendChan stdout s])
- SearchError "Test"
- (11 reductions, 59 cells)
- ?
-
- In each of the examples above, the failure continuation has type
- "FailCont" as defined by the following type synonym in the standard
- prelude:
-
- type FailCont = IOError -> Dialogue
-
- Similarly, the success continuation, which takes a string representing
- an input string and produces a new Dialogue has type "StrCont":
-
- type StrCont = String -> Dialogue
-
- A third kind of continuation is needed for those requests which return
- a response of the form "Success" if successful (e.g. output
- requests). In this case the continuation is simply another dialogue:
-
- type SuccCont = Dialogue
-
- The following list gives the type of each of the six functions
- corresponding to the six different kinds of I/O request described in
- the previous section. Full definitions for each of these functions are
- given in appendix B:
-
- readFile :: String -> FailCont -> StrCont -> Dialogue
- writeFile :: String -> String -> FailCont -> SuccCont -> Dialogue
- appendFile :: String -> String -> FailCont -> SuccCont -> Dialogue
- readChan :: String -> FailCont -> StrCont -> Dialogue
- appendChan :: String -> String -> FailCont -> SuccCont -> Dialogue
- echo :: Bool -> FailCont -> SuccCont -> Dialogue
-
- As an illustration of the use of these functions, we show how each of
- the example programs from the previous section can be rewritten using
- the continuation based style of I/O, starting with the program
- "helloWorld":
-
- helloWorld :: Dialogue
- helloWorld = appendChan stdout "hello, world" abort done
-
- In this case, the explicit type declaration is not actually required
- since the type of the expression is completely determined by the type
- of "appendChan". The failure continuation "abort" is equivalent to the
- function "(\ioerror resps -> [])" described above and terminates the
- program if an error occurs without any further action. In a similar
- way, "done" is the trivial dialogue which terminates immediately
- without any action. Both of these values are defined in the standard
- prelude:
-
- done :: Dialogue
- done resps = []
-
- abort :: FailCont
- abort ioerror = done
-
- Using the same approach, the "showFile" and "main" programs from the
- previous section are written as:
-
- showFile :: String -> Dialogue
- showFile name
- = readFile name (\ioerror -> appendChan stdout
- ("Can't open " ++ name) abort done)
- (\contents-> appendChan stdout contents abort done)
-
- main :: Dialogue
- main = appendChan stdout "Please type a filename: " abort
- (readChan stdin abort
- (\userInput -> let (name : _) = lines userInput in
- readFile name
- (\ioerror -> appendChan stdout ("Can't open " ++ name)
- abort done)
- (\contents -> appendChan stdout contents abort done)))
-
-
- .ST 12.3 Interactive programs
- -------------------------
- One of the principal motivations for including facilities for I/O in
- Gofer programs was to provide a way of using interactive programs as
- described in [1]. An interactive program is represented by a function
- of type String -> String mapping an input string of characters entered
- at the keyboard into an output string to be displayed on the screen.
-
- There are two functions defined in the standard prelude which can be
- used to `execute' functions of this kind as interactive programs:
-
- o "interact f" executes f::String->String as an interactive program
- with echo on. This means that characters are read from the
- keyboard a line at a time. The usual editing characters such as
- backspace can be used to correct mistakes which are noticed before
- the return key is pressed at the end of each line. The input
- stream can be terminated by typing an end of file character at the
- beginning of a line:
-
- ? interact (map toUpper)
- This text was entered using the interact function
- THIS TEXT WAS ENTERED USING THE INTERACT FUNCTION
- ^Z
- (874 reductions, 1037 cells)
- ?
-
- o "run f" behaves like "interact f" except that echo is turned off.
- In this case, the only way of terminating the input stream without
- reaching the end of the string produced by "f" is to use the
- interrupt key:
-
- ? run (map toUpper)
- ALTHOUGH THIS IS ENTERED IN LOWER CASE, IT STILL
- APPEARS IN UPPER CASE !
- {Interrupted!}
-
- (1227 reductions, 1463 cells)
- ?
-
- [ASIDE: of these two functions, only "interact" is also included in the
- standard prelude for Haskell, although "run" may also be added to a
- Haskell system using the definition below.]
-
- The definitions of "interact" and "run" provide further examples of
- Gofer programs using simple I/O facilities:
-
- interact :: (String -> String) -> Dialogue
- interact f = readChan stdin abort
- (\s -> appendChan stdout (f s) abort done)
-
- run :: (String -> String) -> Dialogue
- run f = echo False abort (interact f)
-
- [EXERCISE for the interested reader: construct alternative definitions
- for these functions using the stream based approach from section 12.1.]
-
- .pa
- .co-------------------------------------------------------------------|
- .>ch13
- .ST 13. LAYOUT
-
- .ST 13.1 Comments
- -------------
- Comments provide an informal but useful way of annotating a program
- with a description of its purpose, structure and development.
- Following the definition of Haskell, two styles of comment are
- supported by Gofer:
-
- o A one line comment begins with the two characters "--" and is
- terminated at the end of the same line. Note that an operator
- symbol cannot begin with "--" as this will be treated as the
- beginning of a comment. It is however possible to use the two
- characters "--" at any other position within an operator symbol.
- Thus a line such as:
-
- (xs ++ ys) -- xs
-
- includes a comment and will actually be treated as if the line had
- been written:
- (xs ++ ys)
-
- Whereas the line:
-
- xs >--> ys >--> zs
-
- does not contain any comments (although it will cause an error
- unless ">-->" has been defined using an appropriate infixl or
- infixr declaration).
-
- o A nested comment begins with the characters "{-", ends with the
- characters "-}" and may span any number of lines. [N.B. the
- initial "{-" string cannot overlap with the terminating "-}"
- string so that the shortest possible nested comment is "{--}", and
- not "{-}"]. An unterminated nested comment will be treated as an
- error.
-
- As the name suggests, comments of this kind may be nested so that
- "{- {- ... -} ... {- ... -} -}" is treated as a single comment.
- This makes nested comments particularly convenient for enclosing
- parts of a program which may already contain other nested
- comments.
-
- Both kinds of comment may be used in expressions entered directly into
- the Gofer system, or more usually, in files of definitions loaded into
- Gofer. The two styles of comment may be mixed within the same
- expression or program, remembering that the string "--" has no special
- significance within a nested comment and that the strings "{-" and "-}"
- have no special significance in a single line comment. Thus:
-
- [ 2, -- {- [ 2, {-
- 3, -- -} -- -} 3,
- 4 ] 4 ]
-
- are both equivalent to the list expression [2,3,4].
-
-
- .ST 13.2 The layout rule
- --------------------
- In a tradition dating back at least a quarter of a century to Landin's
- ISWIM family of languages, most Gofer programs use indentation to
- indicate the structure of a program. For example, in a definition such
- as:
-
- f x y = g (x + w)
- where g u = u + v
- where v = u * u
- w = 2 + y
-
- it is clear from the layout that the definition of w is intended to be
- local to f rather than to g. Another example where layout plays an
- important role is in distinguishing the two definitions:
-
- example x y z = a + b example x y z = a + b
- where a = f x y where a = f x
- b = g z y b = g z
-
- There are three situations in Gofer where indentation is typically used
- to determine the structure of a program:
-
- o At the top-level of a file of definitions.
-
- o In a group of local declarations following either of the keywords
- "let" or "where".
-
- o In a group of alternatives in a case expression, following the
- keyword "of".
-
- In each case, Gofer actually expects to find a list of items enclosed
- between braces `{' and `}' with individual items separated from one
- another by semicolons `;'. However, if the leading brace is not found
- then Gofer uses the layout rule described below to arrange for `{', `}'
- and `;' tokens to be inserted into the input stream automatically
- according to the indentation of each line.
-
- In this way, the first example above will in fact be treated as if the
- user had entered:
-
- f x y = g (x + w)
- where {g u = u + v
- where {v = u * u
- }; w = 2 + y
- }
-
- or, equivalently, just:
-
- f x y = g (x + w) where {g u = u + v where {v = u * u}; w = 2 + y}
-
- where the additional punctuation using the `{', `}' and `;' characters
- makes the intended grouping clear, regardless of indentation.
-
- .cc 2
- The layout rule used in Gofer is the same as that of Haskell, and can
- be described as follows:
-
- o An opening brace `{' is inserted in front of the first token at
- the beginning of a file or following one of the keywords "where",
- "let" or "of", unless that token is itself an opening brace.
-
- o A `;' token is inserted in front of the first token in any
- subsequent line with exactly the same indentation as the token in
- front of which the opening brace was inserted.
-
- o The layout rule ends and a `}' token is inserted in front of the
- first token in a subsequent line whose indentation is strictly
- less than that of the token in front of which the opening brace
- was inserted.
-
- o A closing brace `}' will also be inserted at any point where an
- otherwise unexpected token is encountered. This part of the rule
- makes it possible to use expressions such as:
-
- let a = fact 12 in a+a
-
- without needing to use the layout characters explicitly as in:
-
- let {a = fact 12} in a+a.
-
- o Lines containing only whitespace (blanks and tabs) and comments do
- not affect the use of the layout rule.
-
- o For the purposes of determining the indentation of each line in a
- file, tab stops are assumed to be placed every 8 characters, with
- the leftmost tab stop in column 9. Each tab character inserts one
- or more spaces as necessary to move to the next tab stop.
-
- o The indentation of the end of file token is zero.
-
- The following (rather contrived) program, is based on an example in the
- Haskell report [5], and provides an extended example of the use of the
- layout rule. A file containing the following definitions:
-
- data Stack a = Empty
- | MkStack a (Stack a)
-
- push :: a -> Stack a -> Stack a
- push x s = MkStack x s
-
- size :: Stack a -> Int
- size s = length (stkToList s) where
- stkToList Empty = []
- stkToList (MkStack x s) = x:xs where xs = stkToList s
-
- pop :: Stack a -> (a, Stack a)
- pop (MkStack x s) = (x, case s of r -> i r where i x = x)
-
- top :: Stack a -> a
- top (MkStack x s) = x
-
- will be treated by Gofer as if it has been written:
-
- {data Stack a = Empty
- | MkStack a (Stack a)
-
- ;push :: a -> Stack a -> Stack a
- ;push x s = MkStack x s
-
- ;size :: Stack a -> Int
- ;size s = length (stkToList s) where
- {stkToList Empty = []
- ;stkToList (MkStack x s) = x:xs where {xs = stkToList s
-
- }};pop :: Stack a -> (a, Stack a)
- ;pop (MkStack x s) = (x, case s of {r -> i r where {i x = x}})
-
- ;top :: Stack a -> a
- ;top (MkStack x s) = x
- }
-
- Note that some of the more sophisticated forms of expression cannot be
- written on a single line (and hence entered directly into the Gofer
- system) without explicit use of the layout characters `{', `}' and `;':
-
- ? len [1..10] where len [] = 0; len (x:xs) = 1 + len xs
- 10
- (81 reductions, 108 cells)
-
- ? f True where f x = case x of True->n where {n=not x}; False->True
- False
- (4 reductions, 11 cells)
-
- ?
-
- One situation in which the layout rule can cause problems is with
- top-level definitions. For example, the two lines:
-
- f x = 1 + x
- g y = 1 - y
-
- will be treated as a single line "f x = 1 + x g y = 1 - y", which will
- cause a syntax error. This kind of problem becomes rather more
- difficult to spot if the two definitions are not on subsequent lines,
- particularly if they are separated by several lines of comments. For
- this reason, it is usually a good idea to ensure that all of the
- top-level definitions in a file start in the same column (the first
- column is usually the most convenient). COBOL and Fortran programmers
- are not likely to find this problem too distressing :-)
- .pa
- .on
- .co-------------------------------------------------------------------|
- .>ch14
- .ST 14. OVERLOADING IN GOFER
-
- One of the biggest differences between Gofer and most other programming
- languages (with the exception of Haskell) is the approach to
- overloading; enabling the definition and use of functions in which the
- meaning of a function symbol may depend on the types of its arguments.
-
- Like Haskell, overloading in Gofer is based around a system of type
- classes which allow overloaded functions to be grouped together into
- related groups of functions. Whilst the precise details of the
- approach to type classes used by Gofer are quite different from those
- of Haskell, both rely on the same basic ideas and use a similar syntax
- for defining and using type classes. It would therefore seem possible
- that experience gained with the overloading system in one language can
- readily by applied to the other.
-
- The differences embodied in the Gofer system of classes stem from my
- own, theoretically based investigations into `qualified types' some of
- which is detailed in references [8-12]. In my personal opinion, the
- Gofer system has some significant advantages over the Haskell approach
- (see [12] for details) and one of the principal motivations behind the
- implementation to Gofer was to provide a way of testing such claims.
- One fact which I believe has already been established using Gofer is
- that the use and implementation of overloaded functions need not have
- the significant effect on performance that was anticipated with early
- implementations of Haskell.
-
- This section outlines the system of type classes used in Gofer,
- indicating briefly how they can be used and how they are implemented.
-
-
- .ST 14.1 Type classes and predicates
- --------------------------------
- A type class can be thought of as a family of types (or more generally
- as a family of tuples of types) whose elements are called instances of
- the class. If C is the name of an n-parameter type class then an
- expression of the form C t1 t2 ... tn where t1, t2, ..., tn are type
- expressions is called a predicate and represents the assertion that the
- specified tuple of types is an instance of the class C.
-
- Given a polymorphic function (e.g. map::(a->b)->[a]->[b]), we are free
- to use the function at any type which can be obtained by substituting
- arbitrary types for each of the type variables in its type. In Gofer,
- a type expression may be qualified by one or more predicates which
- restrict the range of types at which a value can be used:
-
- e.g. a function of type C a => a -> a -> a can be treated as a function
- of type t -> t -> t for any instance t of the class C.
-
- The predicate C a in the type expression in the previous example is
- called the context of the type. Contexts may contain more than one
- predicate in which case the predicates involved must be separated by
- commas and the context enclosed in parentheses as in (C a, D b). The
- empty context is written () and any type expression t is equivalent to
- the qualified type () => t. For uniformity, a context with only one
- element may also be enclosed by parentheses.
-
- For technical reasons, type synonyms are not currently permitted in
- predicates. This is consistent with the use of predicates in Haskell,
- but may be relaxed, at least in certain cases, in later versions of
- Gofer.
-
-
- .ST 14.2 The type class Eq
- ----------------------
- The type class Eq is a simple and useful example, whose instances are
- precisely those types whose elements can be tested for equality. The
- declaration of this class given in the standard prelude is as follows:
-
- class Eq a where
- (==), (/=) :: a -> a -> Bool
- x /= y = not (x == y)
-
- There are three parts in any class declaration. For this particular
- example we have:
-
- o The first line (called the `header') of the declaration introduces
- a name Eq for the class and indicates that it has a single
- parameter, represented by the type variable a.
-
- o The second line of the declaration (the `signature part')
- indicates that there are functions denoted by the operator symbols
- (==) and (/=) of type a -> a -> Bool for each instance a of class
- Eq. Using the notation introduced in the previous section, both
- of these operators have type:
-
- Eq a => a -> a -> Bool
-
- These functions are called the `members' (or `member functions')
- of the class. [This terminology, taken from Haskell, is rather
- unfortunate; thinking of a type class as a set of types, the
- elements of the class are called `instances', whilst the `members'
- of the class correspond more closely to the instance variables
- that are used in the terminology of object-oriented programming.]
-
- The intention is that the (==) function will be used to implement
- an equality test for each instance of the class, with the (/=)
- operator providing the corresponding inequality function. The
- ability to include related groups of functions within a single
- type class in this way is a useful tool in program design.
-
- o The third line of the class declaration (the `default
- definitions') provides a default definition of the (/=) operator
- in terms of the (==) operator. Thus it is only necessary to give
- a definition for the (==) operator in order to define all of the
- member functions for the class Eq. It is possible to override
- default member definitions by giving an alternative definition as
- appropriate for specific instances of the class.
-
-
- .ST 14.2.1 Implicit overloading
- ---------------------------
- Member functions are clearly marked as overloaded functions by their
- definition as part of a class declaration, but this is not the only way
- in which overloaded functions occur in Gofer; the restriction to
- particular instances of a type class is also carried over into the type
- of any function defined either directly or indirectly in terms of the
- member functions of that class. For example, the types inferred for
- the following two functions:
-
- x `elem` xs = any (x==) xs
- xs `subset` ys = all (`elem` ys) xs
-
- are:
-
- elem :: Eq a => a -> [a] -> Bool
- subset :: Eq a => [a] -> [a] -> Bool
-
- [ASIDE: On the other hand, if none of the functions used in a
- particular expression or definition are overloaded then there will not
- be any overloading in the corresponding value. Gofer does not support
- the concept of implicit overloading used in some languages where a
- value of a particular type might automatically be coerced to a value of
- some supertype. An example of this would be the automatic translation
- of a badly typed expression "1.0 == 1" to a well-typed expression of
- the form "1.0 == float 1" for some (potentially overloaded) coercion
- function "float" mapping numeric values to elements of type Float.]
-
- Note also that the types appearing in the context of a qualified type
- reflect the types at which overloaded functions are used. Thus:
-
- f x ys = [x] == ys
-
- has type Eq [a] => a -> [a] -> Bool, and not Eq a => a -> [a] -> Bool,
- which is the type that would be assigned to "f" in a Haskell system.
-
-
- .ST 14.2.2 Instances of class Eq
- ----------------------------
- Instances of a type class are defined using declarations similar to
- those used to define the corresponding type class. The following
- examples, taken from the standard prelude, give the definitions for a
- number of simple instances of the class Eq:
-
- instance Eq Int where (==) = primEqInt
-
- instance Eq Bool where
- True == True = True
- False == False = True
- _ == _ = False
-
- instance Eq Char where c == d = ord c == ord d
-
- instance (Eq a, Eq b) => Eq (a,b) where
- (x,y) == (u,v) = x==u && y==v
-
- instance Eq a => Eq [a] where
- [] == [] = True
- [] == (y:ys) = False
- (x:xs) == [] = False
- (x:xs) == (y:ys) = x==y && xs==ys
-
- The interpretation of these declarations is as follows:
-
- o The first declaration makes Int an instance of class Eq. The
- function "primEqInt" is a primitive Gofer function which tests the
- equality of two integer values and has type Int -> Int -> Bool.
-
- o The second declaration makes Bool an instance of class Eq with a
- simple definition involving pattern matching.
-
- o The third declaration makes Char an instance of class Eq. This
- definition indicates that a pair of characters are equal if they
- have the same ASCII value, which is obtained using the "ord"
- function. Note that the two occurrences of the symbol (==) in the
- equation:
-
- c == d = ord c == ord d
-
- have different meanings; the first denotes equality between
- characters (elements of type Char), whilst the second denotes
- equality between integers (elements of type Int).
-
- o The fourth declaration provides an equality operation on pairs.
- Given two elements (x,y) and (u,v) of type (a,b) for some a, b, it
- must be possible to check that both x==u and y==v before we can be
- sure that the two pairs are indeed equal. In other words, both a
- and b must also be instances of Eq in order to make (a,b) an
- instance of Eq. This requirement is described by the first line
- in the instance declaration using the expression:
-
- (Eq a, Eq b) => Eq (a,b)
-
- o The fifth declaration makes [a] an instance of Eq, whenever a is
- itself an instance of Eq in a similar way to the previous
- example. The context Eq a is used in the last equation in the
- declaration:
-
- (x:xs) == (y:ys) = x==y && xs==ys
-
- which contains three occurrences of the (==) operator; the first
- and third are used to compare lists of type [a], whilst the second
- is used to compare elements of type a, using the instance Eq a.
-
- Combining these five declarations, we obtain definitions for (==) on an
- infinite family of types including Int, Char, Bool, (Int,Bool),
- (Char,Int), [Char], (Bool,[Int]), [(Bool,Int)], etc...:
-
- ? 2 == 3 -- using Eq Int
- False
- (2 reductions, 10 cells)
- ? (["Hello"],3) == (["Hello"],3) -- using Eq ([[Char]],Int)
- True
- (31 reductions, 65 cells)
- ?
-
- On the other hand, any attempt to use (==) to compare elements of some
- type not covered by a suitable instance declaration will result in an
- error. For example, the standard prelude does not define the equality
- operation on triples of values:
-
- ? (1,2,3) == (1,2,3)
- ERROR: Cannot derive instance in expression
- *** Expression : (==) d125 (1,2,3) (1,2,3)
- *** Required instance : Eq (Int,Int,Int)
- ?
-
- This can be solved by including an instance declaration of the
- following form into a file of definitions loaded into Gofer:
-
- instance (Eq a, Eq b, Eq c) => Eq (a,b,c) where
- (x,y,z) == (u,v,w) = x==u && y==v && z==w
-
- Giving:
-
- ? (1,2,3) == (1,2,3)
- True
- (6 reductions, 20 cells)
- ?
-
- In general, an instance declaration has the form:
-
- instance context => predicate where
- definitions of member functions
-
- The context part of the declaration gives a list of predicates which
- must be satisfied for the predicate on the right hand side of the `=>'
- sign to be valid. Constant predicates (i.e. predicates not involving
- any type variables) required by an instance declaration (such as the
- predicate Eq Int required by the third declaration) need not be
- included in the context. If the resulting context is empty (as in the
- first three declarations above) then it may be omitted, together with
- the corresponding `=>' symbol.
-
-
- .ST 14.2.3 Testing equality of represented values
- ---------------------------------------------
- Instances of Eq can also be defined for other types, including
- user-defined datatypes, and unlike the instances described above, the
- definition of (==) need not be used to determine whether the values
- being compared have the same structure; it is often more useful to
- check that they represent the same value. As an example, suppose that
- we introduce a type constructor Set for representing sets of values,
- using a list to store the values held in the set:
-
- data Set a = Set [a]
-
- As usual, we say that two sets are equal if they have the same members,
- ignoring any repetitions or differences in the ordering of the elements
- in the lists representing the sets. This is achieved using the
- following instance declaration:
-
- instance Eq a => Eq (Set a) where
- Set xs == Set ys = xs `subset` ys && ys `subset` xs
- where xs `subset` ys = all (`elem` ys) xs
-
- A couple of examples illustrate the use of this definition:
-
- ? Set [1,2,3] == Set [3,4,1]
- False
- (49 reductions, 89 cells)
- ? Set [1,2,3] == Set [1,2,2,2,1,3]
- True
- (157 reductions, 240 cells)
- ?
-
-
- .ST 14.2.4 Instance declarations without members
- --------------------------------------------
- It is possible to give an instance declaration without specifying any
- definitions for the member functions of the class. For example:
-
- instance Eq ()
-
- In this case, the definition of (==) for the instance Eq () is left
- completely undefined, and hence so is the definition of (/=), which is
- defined in terms of (==):
-
- ? () == ()
- {undefined_member (==)}
- (3 reductions, 34 cells)
- ? () /= ()
- {undefined_member (==)}
- (4 reductions, 36 cells)
- ?
-
-
- .ST 14.2.5 Equality on function types
- ---------------------------------
- If an expression requires an instance of a class which cannot be
- obtained using the rules in the given instance declarations, then an
- error message will be produced when the expression is type-checked.
- For example, in general there is no sensible way to determine when a
- pair of functions are equal, and the standard prelude does not include
- a definition for an instance of the form Eq (a -> b) for any types a
- and b:
-
- ? (1==) == (\x->1==x)
- ERROR: Cannot derive instance in expression
- *** Expression : (==) d148 ((==) {dict} 1) (\x->(==) {dict} 1 x)
- *** Required instance : Eq (Int -> Bool)
-
- ?
-
- If for some reason, you would prefer this kind of error to produce an
- error message when an expression is evaluated, rather than when it is
- type-checked, you can use an instance declaration to specify the
- required behaviour. For example:
-
- instance Eq (a -> b) where
- (==) = error "Equality not defined between functions"
-
- Evaluating the previous expression once this instance declaration has
- been included now produces the following result:
-
- ? (1==) == (\x->1==x)
- {error "Equality not defined between functions"}
- (42 reductions, 173 cells)
- ?
-
- A limited form of equality can be defined for functions of type (a->b)
- if a has only finitely many elements, such as the boolean type Bool:
-
- instance Eq a => Eq (Bool -> a) where
- f == g = f False == g False && f True == g True
-
- [ASIDE: This instance declaration would not be accepted in a Haskell
- program which insists that the predicate on the right of the `=>'
- symbol contains precisely one type constructor symbol.]
-
- Using this instance declaration once for each argument, we can now test
- two functions taking boolean arguments for equality (assuming of course
- that their result type is also an instance of Eq).
-
- ? (&&) == (||)
- False
- (9 reductions, 21 cells)
- ? not == (\x -> if x then False else True)
- True
- (8 reductions, 16 cells)
- ? (&&) == (\x y-> if x then y else False)
- True
- (16 reductions, 30 cells)
- ?
-
-
- .ST 14.2.6 Non-overlapping instances
- --------------------------------
- Other instance declarations for types of the form a -> b can be used at
- the same time, so long as no pair of declarations overlap. For
- example, adding the following instance declaration
-
- instance Eq a => Eq (() -> a) where f == g = f () == g ()
-
- enables us to evaluate expressions such as:
-
- ? (\()->"Hello") == const "Hello"
- True
- (30 reductions, 55 cells)
- ?
-
- If however, we try to use instance declarations for types of the form
- (a -> b) and (Bool -> a) at the same time, then Gofer produces an error
- message similar to the following:
-
- ERROR "file" (line 37): Overlapping instances for class "Eq"
- *** This instance : Eq (a -> b)
- *** Overlaps with : Eq (Bool -> a)
- *** Common instance : Eq (Bool -> a)
-
- ?
-
- indicating that, given the task of testing two values of type (Bool->a)
- for equality, there are (at least) two definitions of (==) that could
- be used, with potentially different results being obtained in each
- case.
-
- Here is a further example of the use of non-overlapping instances of a
- class to define a function "cat" (inspired by the unix (tm) command of
- the same name) which uses the I/O facilities of Gofer to print the
- contents of one or more files on the terminal:
-
- class Cat a where cat :: a -> Dialogue
- instance Cat [Char] where cat n = showFile n done
- instance Cat [[Char]] where cat = foldr showFile done
-
- showFile name cont = readFile name abort
- (\s->appendChan stdout s abort cont)
-
- Given these declarations, an expression of the form:
-
- cat "file"
-
- can be used to display the contents of the named file, whilst a list of
- files can be printed one after the other using an expression of the
- form:
-
- cat ["file1", "file2", ..., "filen"].
-
-
- .ST 14.3 Dictionaries
- -----------------
- In order to understand some of the messages produced by Gofer, as well
- as some of the more subtle problems associated with overloaded
- functions, it is useful to have a rough idea of the way in which
- overloaded functions are implemented.
-
- The basic idea is that a function with a qualified type context => type
- where context is a non-empty list of predicates is implemented by a
- function which takes an extra argument for each predicate in the
- context. When the function is used, each of these parameters is filled
- by a `dictionary' which gives the values of each of the member
- functions in the appropriate class. None of these extra parameters is
- entered by the programmer. Instead, they are inserted automatically
- during type-checking.
-
- For the class Eq, each dictionary has at least two elements containing
- definitions for each of the functions (==) and (/=). A dictionary for
- an instance of Eq can be depicted by a diagram of the form:
-
- +--------+--------+---------
- | | |
- | (==) | (/=) | .....
- | | |
- +--------+--------+---------
-
- In order to produce useful error messages and indicate the way in which
- dictionary expressions are being used, Gofer uses a number of special
- notations for printing expressions involving dictionaries:
-
- (#1 d) selects the first element of the dictionary d
-
- (#2 d) selects the second element of the dictionary d
-
- (#n d) selects the nth element of the dictionary d
- (note that (#0 d) is equivalent to the dictionary d).
-
- {dict} denotes a specific dictionary (the contents are not
- displayed).
-
- dnnn a dictionary variable representing an unknown dictionary is
- printed as a lower case letter `d' followed by an integer;
- e.g. d231.
-
- Note that, whilst these notations are used in output produced by Gofer
- and in the following explanations, they cannot be entered directly into
- Gofer expressions or programs -- even if you use a variable such as
- "d1" in an expression, Gofer will not confuse this with a dictionary
- variable with the same name (although Gofer might confuse you by using
- the same name in two different contexts!).
-
- Using these notations, the member functions (==) and (/=) of the class
- Eq behave as if they were defined by the expressions:
-
- (==) d1 = (#1 d1)
- (/=) d1 = (#2 d1)
-
- To understand how these definitions work, we need to take a look at a
- specific dictionary. Following the original instance declaration used
- for Eq Int, the corresponding dictionary is:
-
- d :: Eq Int
- +------------+------------+
- | | |
- | primEqInt | defNeq d |
- | | |
- +------------+------------+
-
- Note that the dictionary variable d is used as a name for the
- dictionary in this diagram, indicating how values within a dictionary
- can include references to the same dictionary.
-
- [ASIDE: It turns out that predicates play a very similar role for
- dictionaries as types play for normal values. This motivates our use
- of the notation d :: Eq Int to indicate that d is a dictionary for the
- instance Eq Int. One difference between these, particularly important
- for theoretical work, is that dictionary values are uniquely determined
- by predicates; if d1::p and d2::p for some predicate p, then d1 = d2.]
-
- The value held in the first element of the dictionary is the primitive
- equality function on integers, "primEqInt". The following diagram
- shows how the dictionary is used to evaluate the expression "2 == 3".
- Note that this expression will first be translated to "(==) d 2 3" by
- the type checker. The evaluation then proceeds as follows:
-
- (==) d 2 3 ==> (#1 d) 2 3
- ==> primEqInt 2 3
- ==> False
-
- The second element of the dictionary is a little more interesting
- because it uses the default definition for (/=) given in the original
- class definition which, after translation, is represented by the
- function "defNeq" defined by:
-
- defNeq d1 x y = not ((==) d1 x y)
-
- Notice the way in which the extra dictionary parameter is used to
- obtain the appropriate overloading. For example, evaluation of the
- expression "2 /= 3", which becomes "(/=) d 2 3" after translation,
- proceeds as follows:
-
- (/=) d 2 3 ==> (#2 d) 2 3
- ==> defNeq d 2 3
- ==> not ((==) d 2 3)
- ==> not ((#1 d) 2 3)
- ==> not (primEqInt 2 3)
- ==> not False
- ==> True
-
- [Clearly there is some scope for optimisation here; whilst the actual
- reduction sequences used by Gofer are equivalent to those illustrated
- above, the precise details are a little different.]
-
- If an instance is obtained from an instance declaration with a
- non-empty context, then the basic two element dictionary used in the
- examples above is extended with an extra dictionary value for each
- predicate in the context. As an example, the diagram below shows the
- dictionaries that will be created from the instance definitions in
- section 14.2.2 for the instance Eq (Int, [Int]). The functions
- "eqPair" and "eqList" which are used in these dictionaries are obtained
- from the definitions of (==) given in the instance declarations for Eq
- (a,b) and Eq [a] respectively:
-
- eqPair d (x,y) (u,v) = (==) (#3 d) x u && (==) (#4 d) y v
-
- eqList d [] [] = True
- eqList d [] (y:ys) = False
- eqList d (x:xs) [] = False
- eqList d (x:xs) (y:ys) = (==) (#3 d) x y && (==) d xs ys
-
- The dictionary structure for Eq (Int, [Int]) is as follows. Note that
- the Gofer system ensures that there is at most one dictionary for a
- particular instance of a class, and that the dictionary d1 :: Eq Int in
- this system is automatically shared between d2 and d3:
-
- d3 :: Eq (Int, [Int])
- +------------+------------+------------+------------+
- | | | | |
- | eqPair d3 | defNeq d3 | d1::Eq Int |d2::Eq [Int]|
- | | | | |
- +------------+------------+-----+------+-----+------+
- | |
- +--------------+ |
- | |
- | d2 :: Eq [Int] V
- | +------------+------------+------------+
- | | | | |
- | | eqList d2 | defNeq d2 | d1::Eq Int |
- | | | | |
- | +------------+------------+-----+------+
- | |
- d1 :: Eq Int V |
- +------------+------------+ |
- | | | |
- | primEqInt | defNeq d1 |<--------------------------+
- | | |
- +------------+------------+
-
- Once again, it may be useful to see how these definitions are used to
- evaluate the expression "(2,[1]) == (2,[1,3])" which, after
- translation, becomes "(==) d3 (2,[1]) (2,[1,3])":
-
- (==) d3 (2,[1]) (2,[1,3])
- ==> (#1 d3) (2,[1]) (2,[1,3])
- ==> eqPair d3 (2,[1]) (2,[1,3])
- ==> (==) (#3 d3) 2 2 && (==) (#4 d3) [1] [1,3]
- ==> (==) d1 2 2 && (==) (#4 d3) [1] [1,3]
- ==> (#1 d1) 2 2 && (==) (#4 d3) [1] [1,3]
- ==> primEqInt 2 2 && (==) (#4 d3) [1] [1,3]
- ==> True && (==) (#4 d3) [1] [1,3]
- ==> (==) (#4 d3) [1] [1,3]
- ==> (==) d2 [1] [1,3]
- ==> (#1 d2) [1] [1,3]
- ==> eqList d2 [1] [1,3]
- ==> (==) (#3 d2) 1 1 && (==) d2 [] [3]
- ==> (==) d1 1 1 && (==) d2 [] [3]
- ==> (#1 d1) 1 1 && (==) d2 [] [3]
- ==> primEqInt 1 1 && (==) d2 [] [3]
- ==> True && (==) d2 [] [3]
- ==> (==) d2 [] [3]
- ==> False
-
-
- .ST 14.3.1 Superclasses
- -------------------
- In general, a type class declaration has the form:
-
- class context => Class a1 ... an where
- type declarations for member functions
- default definitions of member functions
-
- where Class is the name of the new type class which takes n arguments,
- represented by distinct type variables a1, ..., an. As in the case of
- instance declarations, the context that appears on the left hand side
- of the `=>' symbol specifies a list of predicates that must be
- satisfied in order to construct any instance of "Class".
-
- The predicates in the context part of a class declaration are called
- the superclasses of Class. This terminology is taken from Haskell
- where all classes have a single parameter and each of the predicates in
- the context part of a class declaration has the form C a1; in this
- situation, any instance of Class must also be an instance of each class
- C named in the context. In other words, each such C contains a
- superset of the types in Class.
-
- As an example of a class declaration with a non-empty context, consider
- the following declaration from the standard prelude which introduces a
- class Ord whose instances are types with both strict (<), (>) and
- non-strict (<=), (>=) versions of an ordering defined on their
- elements:
-
- class Eq a => Ord a where
- (<), (<=), (>), (>=) :: a -> a -> Bool
- max, min :: a -> a -> a
-
- x < y = x <= y && x /= y
- x >= y = y <= x
- x > y = y < x
-
- max x y | x >= y = x
- | y >= x = y
- min x y | x <= y = x
- | y <= x = y
-
- Notice that this definition provides default definitions for all of the
- member functions except (<=), so that in general only this single
- function needs to be defined to construct an instance of class Ord.
-
- There are two reasons for defining Eq as a superclass of Ord:
-
- o The default definition for (<) relies on the use of (/=) taken
- from class Eq. In order to guarantee that this is always valid we
- must ensure that every instance of Ord must also be an instance of
- Eq.
-
- o Given the definition of a non-strict ordering (<=) on the elements
- of a type, it is always possible to construct a definition for the
- (==) operator (and hence for (/=)) using the equation:
-
- x==y = x<=y && y<=x
-
- There will therefore be no loss in generality by requiring Eq to
- be a superclass of Ord, and conversely, no difficulty in defining
- an instance of Eq to accompany any instance of Ord for which an
- instance of Eq has not already be provided.
-
- As an example, the following definitions provide an alternative
- way to implement the equality operation on elements of the Set
- datatype described in section 14.2.3, in terms of the subset
- ordering defined in class Ord:
-
- instance Ord (Set a) => Eq (Set a) where
- x == y = x <= y && y <= x
-
- instance Eq a => Ord (Set a) where
- Set xs <= Set ys = all (`elem` ys) xs
-
- This definition is in fact no less efficient or effective than the
- original version.
-
- Dictionaries for superclasses are dealt with in much the same way as
- the instance specific dictionaries described above. For example, the
- general layout of a dictionary for an instance of Ord is illustrated in
- the following diagram:
-
- +--------+--------+--------+--------+--------+--------+--------+-----
- | | | | | | | |
- | (<) | (<=) | (>) | (>=) | max | min | Eq a | .....
- | | | | | | | |
- +--------+--------+--------+--------+--------+--------+--------+-----
-
- Note the use of the seventh element of this dictionary which points to
- the dictionary for the appropriate instance of Eq. This is used in the
- translation of the default definition for (<) which is equivalent to:
-
- defLessThan d x y = (<=) d x y && (/=) (#7 d) x y
-
-
- .ST 14.3.2 Combining classes
- ------------------------
- In general, a dictionary is made up of three separate parts:
-
- +-------------------+-------------------+-------------------+
- | Implementation | Superclass | Instance specific |
- | of class members | Dictionaries | Dictionaries |
- | | | |
- +-------------------+-------------------+-------------------+
-
- Each of these may be empty. We have already seen examples in which
- there are no superclass dictionaries (e.g. instances of Eq) and in
- which there are no instance specific dictionaries (e.g. Eq Int).
- Classes with no member functions (corresponding to dictionaries with no
- member functions) are sometimes useful as a convenient abbreviation for
- a list of predicates. For example:
-
- class C a where cee :: a -> a
- class D a where dee :: a -> a
-
- class (C a, D a) => CandD a
-
- makes CandD a an abbreviation for the context (C a, D a). Thinking of
- single parameter type classes as sets of types, the type class CandD
- corresponds to the intersection of classes C and D.
-
- Just as the type inferred for a particular function definition or
- expression does not involve type synonyms unless explicit type
- signatures are used, the Gofer type system will not use a single
- predicate of the form CandD a instead of the two predicates C a and D a
- unless explicit signatures are used:
-
- ? :t dee . cee
- \d129 d130 -> dee d130 . cee d129 :: (C a, D a) => a -> a
- ? :t dee . cee :: CandD a => a -> a
- \d129 -> dee (#2 d129) . cee (#1 d129) :: CandD a => a -> a
- ?
-
- In Haskell, all instances of a class such as CandD must have
- explicit declarations, in addition to the corresponding declarations
- for instances for C and D. This problem can be avoided by using the
- more general form of instance declaration permitted in Gofer; a single
- instance declaration:
-
- instance CandD a
-
- is all that is required to ensure that any instance of CandD can be
- obtained, so long as corresponding instances for C and D can be found.
-
-
- .ST 14.3.3 Simplified contexts
- --------------------------
- Consider the function defined by the following equation:
-
- eg1 x = [x] == [x] || x == x
-
- This definition does not restrict the type of x in any way except that,
- if x :: a, then there must be instances Eq [a] and Eq a which are used
- for the two occurrences of the (==) operator in the equation. We might
- therefore expect the type of eg1 to be:
-
- (Eq [a], Eq a) => a -> Bool
-
- with translation:
-
- eg1 d1 d2 x = (==) d1 [x] [x] || (==) d2 x x
-
- However, as can be seen from the case where a=Int illustrated in
- section 14.3, given d1::Eq [a] we can always find a dictionary for Eq a
- by taking the third element of d1 i.e. (#3 d1)::Eq a. Since it is more
- efficient to select an element from a dictionary than to complicate
- both type and translation with extra parameters, the type assigned to
- "eg1" by default is:
-
- Eq [a] => a -> Bool
-
- with translation:
-
- eg1 d1 x = (==) d1 [x] [x] || (==) (#3 d1) x x
-
- In general, given a set of predicates corresponding to the instances
- required by an expression, Gofer will always attempt to find the
- smallest possible subset of these predicates such that all of the
- required dictionaries can still be obtained, whilst minimising the
- number of dictionary parameters that are used.
-
- The original type and translation for eg1 given above can be produced
- by including an explicit type signature in the file containing the
- definition of eg1:
-
- eg1 :: (Eq [a], Eq a) => a -> Bool
- eg1 x = [x] == [x] || x == x
-
- But even with this definition, Gofer will still always try to minimise
- the number of dictionaries used in any particular expression:
-
- ? :t eg1
- \d153 -> eg1 d153 (#3 d153) :: Eq [a] => a -> Bool
- ?
-
- As another example, consider the expression "(\x y-> x==x || y==y)".
- The type and translation assigned to this term can be found directly
- using Gofer:
-
- ? :t (\x y-> x==x || y==y)
- \d121 d122 x y -> (==) d122 x x ||
- (==) d121 y y
- :: (Eq b, Eq a) => a -> b -> Bool
- ?
-
- Note that the translation has two dictionary parameters d121 and d122
- corresponding to the two predicates Eq a and Eq b respectively. Since
- both of these dictionaries can be obtained from a dictionary for the
- predicate Eq (a,b), we can use an explicit type signature to produce a
- translation which needs only one dictionary parameter:
-
- ? :t (\x y-> x==x || y==y) :: Eq (a,b) => a -> b -> Bool
- \d121 x y -> (==) (#3 d121) x x ||
- (==) (#4 d121) y y
- :: Eq (a,b) => a -> b -> Bool
- ?
-
-
- .cc 8
- .ST 14.4 Other issues
- -----------------
-
- .ST 14.4.1 Unresolved overloading
- -----------------------------
- Consider the use of the (==) operator in the following three
- situations:
-
- o In the expression "2 == 3", it is clear that the appropriate value
- for the equality operator in this case is primIntEq as defined by
- the instance declaration for Eq Int. The expression can therefore
- be translated to "primEqInt 2 3".
-
- o In the function definition "f x = x==x", we cannot completely
- determine the appropriate value for (==) because it depends on the
- type assigned to the variable "x", which may itself vary with
- different uses of the function "f". It is however possible to add
- an extra parameter to the definition, giving "f d x = (==) d x x"
- and taking the type of "f" to be Eq a => a -> Bool.
-
- In this way, the problem of finding the appropriate definition for
- the (==) operator is deferred until the function is actually used.
-
- o In the expression "[]==[]", the appropriate value for (==) must be
- obtained from the dictionary for some instance of the form Eq [a],
- but there is not sufficient information in the expression to
- determine what the value of the type variable a should be.
-
- Looking back to the instance declaration for Eq [a], we find that
- the definition of (==) depends on the value of the dictionary for
- the instance Eq a. In this particular case, it is clear that the
- expression will always evaluate to True, regardless of the value
- of this dictionary. Unfortunately, the only way that this can be
- detected is by evaluating the expression to see if the calculation
- can be completed without reference to the dictionary value (see
- the comments in the aside at the end of this section).
-
- Attempting to evaluate this expression in Gofer will therefore
- result in an error message indicating that the expression does not
- contain sufficient information to resolve the use of overloading
- in the expression:
-
- ? [] == []
- ERROR: Unresolved overloading
- *** type : Eq [a] => Bool
- *** translation : \d129 -> (==) d129 [] []
- ?
-
- Note that the expression has been converted into a lambda
- expression using the dictionary variable d129 to represent the
- dictionary for the unknown instance Eq [a].
-
- One simple way to resolve the overloading in an expression of this
- kind is to use an explicit type signature. For example, if we
- specify that the second empty list is an empty list of type [Int]:
-
- ? [] == ([]::[Int])
- True
- (2 reductions, 9 cells)
- ?
-
- The same problem occurs in Haskell, where it is described using the
- idea of an `ambiguous type' -- i.e. a type expression of the form
- context => type where one or more of the type variables appearing in
- the given context do not appear in the remaining part of the type
- expression.
-
- Further examples of unresolved overloading occur with other classes.
- As an example consider the class Reader defined by:
-
- class Reader a where
- parse :: String -> a
- unparse :: a -> String
-
- whose member functions provide methods for obtaining the string
- representation of an element of an instance type, and for converting
- such representations back into the original values. (The standard
- Haskell Text class contains similar functions.) Now consider the
- expression "parse . unparse" which maps values from some instance of
- Reader to values of another instance via an intermediate string
- representation.
-
- ? parse . unparse
- ERROR: Unresolved overloading
- *** type : (Reader a, Reader b) => a -> b
- *** translation : \d129 d130 -> parse d130 . unparse d129
- ?
-
- One of the first things that might surprise the reader here is that the
- value produced by "parse . unparse" does not have to be of the same
- type as the argument; for example, we would not usually expect to have
- any sensible interpretation for a floating point number obtained from
- the string representation of a boolean value!
-
- This can be fixed by using an explicit type declaration, although the
- expression still produces unresolved overloading:
-
- ? (parse . unparse) :: Reader a => a -> a
- ERROR: Unresolved overloading
- *** type : Reader a => a -> a
- *** translation : \d130 -> parse d130 . unparse d130
- ?
-
- Notice however that the type of this expression is not ambiguous so
- that the unresolved overloading in this example can be eliminated when
- the function is actually used:
-
- ? ((parse . unparse) :: Reader a => a -> a) 'a'
- 'a'
- (4 reductions, 11 cells)
- ?
-
- A more serious problem occurs with the expression "unparse . parse"
- which maps string values to string values via some intermediate type.
- Clearly this will lead to a problem with unresolved overloading:
-
- ? unparse . parse
- ERROR: Unresolved overloading
- *** type : Reader a => String -> String
- *** translation : \d130 -> unparse d130 . parse (#0 d130)
- ?
-
- Notice that the type obtained in this case is ambiguous; the type
- variable a which appears in the predicate Reader a does not appear in
- the type String -> String. There are a number of ways of resolving
- this kind of ambiguity:
-
- o Using an explicitly typed expression: Assuming for example that
- Char is an instance of Reader, we can write:
-
- ? unparse . (parse :: String -> Char)
- v113 {dict} . v112 {dict}
- (5 reductions, 42 cells)
- ?
-
- without any ambiguity. If such type signatures are used in a
- number of places, it might be better to define an auxiliary
- function and use that instead:
-
- charParse :: String -> Char
- charParse = parse
-
- ? unparse . charParse
- v113 {dict} . charParse
- (4 reductions, 37 cells)
- ?
-
- In such situations, it is perhaps worth asking if overloaded
- functions are in fact the most appropriate solution for the
- problem at hand!
-
- o Using an extra dummy parameter in a function definition. In a
- definition such as:
-
- f = unparse . parse
-
- we can introduce an additional dummy parameter `x' which is not
- used except to determine the type of the result produced by parse
- in f:
-
- f x = unparse . (parse `asTypeOf` (\""->x))
-
- where the standard prelude operator `asTypeOf` defined by:
-
- asTypeOf :: a -> a -> a
- x `asTypeOf` _ = x
-
- is used to ensure that the type of parse in the definition of f is
- the same as that of the function (\""->x) -- in other words, the
- type must be String -> a where a is the type of the variable x.
-
- The resulting type for f is:
-
- f :: Reader a => a -> String -> String
-
- Notice how the addition of the dummy parameter has been used to
- eliminate the ambiguity present in the original type.
-
- This kind of `coding trick' is rather messy and is not recommended
- for anything but the simplest examples.
-
- [ASIDE: The idea of evaluating an expression with an ambiguous type to
- see if it does actually need the unspecified dictionaries could have
- been implemented quite easily in Gofer using an otherwise unused
- datatype Unresolved and generating instance declarations such as:
-
- instance Eq Unresolved where
- (==) = error "unresolved overloading for (==)"
- (/=) = error "unresolved overloading for (/=)"
-
- for each class. Given a particular expression, we can then use the
- type Unused in place of any ambiguous type variables in its type. The
- evaluation of the expression could then be attempted, either completing
- successfully if the dictionaries are not required, but otherwise
- resulting in a run-time error.
-
- This approach is not used in Gofer; instead, the programmer is notified
- of any unresolved polymorphism when the program is type checked,
- avoiding the possibility that a program might contain an undetected
- ambiguity.]
-
-
- .ST 14.4.2 `Recursive' dictionaries
- -------------------------------
- Unlike Haskell, there are no restrictions on the form of the predicates
- that may appear in the context part of a Gofer class or instance
- declaration. This has a number of potentially useful applications
- because it enables the Gofer programs to use mutually `recursive'
- systems of dictionaries.
-
- One example of this is the ability to implement a large family of
- related functions using a group of classes instead of having to use a
- single class. The following example illustrates the technique with an
- alternative definition for the class Eq in which the (==) and (/=)
- operators are placed in different classes:
-
- class Neq a => Eq a where (==) :: a -> a -> Bool
-
- class Eq a => Neq a where (/=) :: a -> a -> Bool
- x/=y = not (x == y)
-
-
- [ASIDE: These declarations clash with those in the standard prelude and
- hence cannot actually be used in Gofer unless a modified version of the
- standard prelude is used instead.]
-
- If we then give instance declarations:
-
- instance Eq Int where (==) = primEqInt
- instance Neq Int
-
- and try to evaluate the expression "2==3" then the following system of
- dictionaries will be generated:
-
- d1 :: Eq Int d2 :: Neq Int
- +-----------+-----------+ +-----------+-----------+
- | | | | | |
- +-->| primEqInt |d2::Neq Int+----->| defNeq d2 |d1::Eq Int +---+
- | | | | | | | |
- | +-----------+-----------+ +-----------+-----------+ |
- | |
- +------------------------------<-------------------------------+
-
- where the function "defNeq" is derived from the default definition in
- the class Neq and is equivalent to:
-
- defNeq d x y = not ((==) (#2 d) x y)
-
- Incidentally, if the instance declaration for Neq Int above had been
- replaced by:
-
- instance Neq a
-
- then the effect of these declarations would be similar to the standard
- definition of the class Eq, except that it would not be possible to
- override the default definition for (/=). In other words, this
- approach would give the same effect as defining (/=) as a top-level
- function rather than a member function in the class Eq:
-
- class Eq a where (==) :: a -> a -> Bool
-
- (/=) :: Eq a => a -> a -> Bool
- x /= y = not (x == y)
-
- There are other situations in which recursive dictionaries of the kind
- described above can be used. A further example is given in the
- following section. Unfortunately, the lack of restrictions on the form
- of class and instance declarations can also lead to problems in some
- (mostly pathological) cases. As an example, consider the class:
-
- class Bad [a] => Bad a where bad :: a -> a
-
- Without defining any instances of Bad, it is not possible to construct
- any dictionaries for instances of Bad:
-
- ? bad 2
- ERROR: Cannot derive instance in expression
- *** Expression : bad d126 2
- *** Required instance : Bad Int
- ?
-
- If however we add the instance declarations:
-
- instance Bad Int where bad = id
- instance Bad [a] where bad = id
-
- then any attempt to construct a dictionary for Bad Int will also
- require a dictionary for the superclass Bad [Int] and then for the
- superclass of that instance Bad [[Int]] etc... Since Gofer has only a
- finite amount of space for storing dictionaries, this process will
- eventually terminate when that space has been used up:
-
- ? bad 2
- ERROR: Dictionary storage space exhausted
- ?
-
- [ASIDE: depending on the configuration of your particular version of
- Gofer and on the nature of the class and instance declarations that are
- involved, an alternative error message "ERROR: Too many type variables
- in type checker" may be produced instead of the message shown above.]
-
- From a practical point of view, this problem is unlikely to cause too
- many real difficulties:
-
- o Class declarations involving predicates such as those in the
- declaration of Bad are unlikely to be used in realistic programs.
-
- o All dictionaries are constructed before evaluation begins. This
- process is guaranteed to terminate because each new dictionary
- that is created uses up part of the space used to hold Gofer
- dictionaries. The construction process will either terminate
- successfully once complete, or be aborted as soon as all of the
- dictionary space has been used.
-
- It remains to see what impact (if any) this has on realistic programs,
- and if later versions of Gofer should be modified to impose some
- syntactic restrictions (as in Haskell) or perhaps some form of static
- checking of the contexts appearing in class and instance declarations.
-
-
- .ST 14.4.3 Classes with multiple parameters
- ---------------------------------------
- Gofer is the first language to support the use of type classes with
- multiple parameters. This again is an experimental feature of the
- language, intended to make it possible to explore the claims from a
- number of researchers about the use of such classes.
-
- Initial experiments suggest that multiple parameter type classes are
- likely to lead to large numbers of problems with unresolved
- overloading. Ultimately, this may mean that such classes are only of
- practical use in explicitly typed languages, or alternatively that a
- more powerful and general defaulting mechanism (similar to that used in
- Haskell with numeric classes) is required to support user controlled
- overloading resolution.
-
- The following declaration introduces a class Iso whose elements are
- pairs of isomorphic types:
-
- class Iso b a => Iso a b where iso :: a -> b
-
- The single member function "iso" represents the isomorphism mapping
- elements of type a to corresponding elements of type b. Note the
- `superclass' context in this declaration which formalises the idea that
- if a is isomorphic to b then b is also isomorphic to a. The class Iso
- therefore provides further examples of the recursive dictionaries
- described in the previous section.
-
- The fact that any type is isomorphic to itself can be described by the
- following instance declaration:
-
- instance Iso a a where iso x = x
-
- For example, the dictionary structure created in order to evaluate the
- expression "iso 2 = 3" is:
-
- d :: Iso Int Int
- +--------------+--------------+
- | | |
- +-->| id |d::Iso Int Int+--+
- | | | | |
- | +--------------+--------------+ |
- | |
- +------------------<-----------------+
-
- ? iso 2 == 3
- False
- (4 reductions, 11 cells)
- ?
-
- Our first taste of the problems to come occurs when we try to evaluate
- the expression "iso 2 == iso 3":
-
- ? iso 2 == iso 3
- ERROR: Unresolved overloading
- *** type : (Eq a, Iso Int a) => Bool
- *** translation : \d130 d132 -> (==) d130 (iso d132 2) (iso d132 3)
- ?
-
- In this case, the "iso" function is used to map the integers 2 and 3 to
- elements of some type a, isomorphic to Int, and the values produced are
- then compared using (==) at the instance Eq a; there is no way of
- discovering what the value of a should be without using an explicit
- type signature.
-
- Further instances can be defined. The following two declarations are
- needed to describe the (approximate) isomorphism between lists of pairs
- and pairs of lists:
-
- instance Iso [(a,b)] ([a],[b]) where
- iso xs = (map fst xs, map snd xs)
-
- instance Iso ([a],[b]) [(a,b)] where
- iso (xs,ys) = zip xs ys
-
- Unfortunately, even apparently straightforward examples give problems
- with unresolved overloading, forcing the use of explicit type
- declarations:
-
- ? iso [(1,2),(3,4)]
- ERROR: Unresolved overloading
- *** type : Iso [(Int,Int)] a => a
- *** translation : \d126 -> iso d126 [(1,2),(3,4)]
-
- ? (iso [(1,2),(3,4)]) :: ([Int],[Int])
- ([1, 3],[2, 4])
- (22 reductions, 64 cells)
- ?
-
- A second example of a multiple parameter type class is defined as
- follows:
-
- class Ord a => Collects a b where
- emptyCollection :: b
- addToCollection :: a -> b -> b
- listCollection :: b -> [a]
-
- The basic intuition is that the predicate Collects a b indicates that
- elements of type b can be used to represent collections of elements of
- type a. A number of people have suggested using type classes in this
- way to provide features similar to the (similarly named, but otherwise
- different) classes that occur in object-oriented languages.
-
- Obvious implementations involve the use of ordered lists or binary
- search trees defined by instances of the form:
-
- data STree a = Empty | Node a (STree a) (STree a)
-
- instance Collects a [a] where ....
- instance Collects a (STree a) where ....
-
- Once again, there are significant problems even with simple examples
- using these functions. As an example, the standard way of defining a
- function of type:
-
- Collects a b => [a] -> b
-
- mapping a list of values to a collection of those values using the
- higher order function "foldr":
-
- listToCollection = foldr addToCollection emptyCollection
-
- actually produces a function with ambiguous type:
-
- ? :t foldr addToCollection emptyCollection
- \d139 d140 -> foldr (addToCollection d140) (emptyCollection d139)
- :: (Collects c b, Collects a b) => [a] -> b
- ?
-
- which cannot be resolved, even with an explicit type declaration.
-
-
- .ST 14.4.4 Overloading and numeric values
- -------------------------------------
- One of the most common uses of overloading is to allow the use of the
- standard arithmetic operators such as (+), (*) etc. on the elements of
- a range of numeric types including integers and floating point values
- in addition to user defined numeric types such as arbitrary precision
- integers, complex and rational numbers, vectors and matrices,
- polynomials etc. In Haskell, these features are supported by a number
- of built-in types and a complex hierarchy of type classes describing
- the operations defined on the elements of each numeric type.
-
- As an experimental language, intended primarily for the investigation
- of general purpose overloading, Gofer has only two built-in numeric
- types; Int and Float (the second of which is not supported in all
- implementations). Similarly, although the Gofer system could be used
- to implement the full hierarchy of Haskell numeric classes, the
- standard prelude uses a single numeric type class Num defined by:
-
- class Eq a => Num a where -- simplified numeric class
- (+), (-), (*), (/) :: a -> a -> a
- negate :: a -> a
- fromInteger :: Int -> a
-
- The first four member functions (+), (-), (*), (/) are the standard
- arithmetic functions on instances of Num, whilst "negate" denotes unary
- negation. The final member function, fromInteger is used to coerce any
- integer value to the corresponding value in another instance of Num.
- An expression such as "fromInteger 3" is called an overloaded numeric
- constant and has type Num a => a indicating that it can be used as a
- value of any instance of Num. See below for examples.
-
- Both Float and Int are defined as instances of Num using primitive
- functions for integer and floating point arithmetic:
-
- instance Num Int where
- (+) = primPlusInt
- (-) = primMinusInt
- (*) = primMulInt
- (/) = primDivInt
- negate = primNegInt
- fromInteger x = x
-
- instance Num Float where
- (+) = primPlusFloat
- (-) = primMinusFloat
- (*) = primMulFloat
- (/) = primDivFloat
- negate = primNegFloat
- fromInteger = primIntToFloat
-
- These definitions make it possible to evaluate numeric expressions
- involving both types:
-
- ? 2 + 3
- 5
- (3 reductions, 6 cells)
- ? 3.2 + 4.321
- 7.521
- (3 reductions, 13 cells)
- ?
-
- Note however that any attempt to evaluate an expression mixing
- different arithmetic types is likely to cause a type error:
-
- ? 4.2 * 4
- ERROR: Type error in application
- *** expression : 4.2 * 4
- *** term : 4.2
- *** type : Float
- *** does not match : Int
- ?
-
- Further problems occur when we try to define functions intended to be
- used with arbitrary instances of Num rather than specific numeric
- types. As an example of this, the standard prelude function "sum",
- roughly equivalent to:
-
- sum [] = 0
- sum (x:xs) = x + sum xs
-
- has type [Int] -> Int, rather than the more general Num a => [a] -> a
- which could be used to find the sum of a list of numeric values in any
- instance of Num. The problem in this particular case is caused by the
- integer constant 0 in the first line of the definition. Replacing this
- with the expression fromInteger 0 leads to the following definition for
- a generic sum function of the required type:
-
- genericSum :: Num a => [a] -> a
- genericSum [] = fromInteger 0
- genericSum (x:xs) = x + genericSum xs
-
- For example:
-
- ? genericSum [1,2,3]
- 6
- (10 reductions, 18 cells)
- ? genericSum [1.0,2.0,3.0]
- 6.0
- (11 reductions, 27 cells)
- ?
-
- The fromInteger function can also be used to solve the previous
- problem:
-
- ? 4.2 * fromInteger 4
- 16.8
- (3 reductions, 13 cells)
- ?
-
- In Haskell, any integer constant k appearing in an expression is
- treated as if the programmer had actually written "fromInteger k" so
- that both of the preceding problems are automatically resolved.
- Unfortunately, this also creates some new problems; applying the
- function fromInteger to each integer constant in the previous examples
- causes problems with unresolved overloading:
-
- ? fromInteger 2 + fromInteger 3
- ERROR: Unresolved overloading
- *** type : Num a => a
- *** translation : \d143 -> (+) d143 (fromInteger d143 2)
- (fromInteger d143 3)
- ?
-
- Once again, Haskell provides a solution to this problem in the form of
- a `default mechanism' for numeric types which, once the following
- problem has been detected, will typically `default' the unknown type
- represented by the type variable a above to be Int, so that the result
- is actually equivalent to the following:
-
- ? (fromInteger 2 + fromInteger 3) :: Int
- 5
- (4 reductions, 8 cells)
- ?
-
- There are a number of problems with the Haskell default mechanism; both
- theoretical and practical. In addition, if a default mechanism of some
- form is used then it should also be capable of dealing with arbitrary
- user-defined type classes, rather than a small group of `standard'
- classes, in order to provide solutions to the unresolved overloading
- problems described in previous sections. Therefore, for the time
- being, Gofer does not support any form of default mechanism and
- overloaded numeric constants can only be obtained by explicit use of
- the fromInteger function.
-
-
- .ST 14.4.5 Constants in dictionaries
- --------------------------------
- The Gofer system constructs new dictionaries as necessary, and deletes
- them when they are no longer required. At any one time, there is at
- most one dictionary for each instance of a class. Coupled with lazy
- evaluation, this has a number of advantages for classes in which member
- functions are defined by variable declarations as in section 9.10. As
- an example, consider the class Finite defined by:
-
- class Finite a where members :: [a]
-
- The only member in this class is a list enumerating the elements of the
- type. For example:
-
- instance Finite Bool where members = [False, True]
-
- instance (Finite a, Finite b) => Finite (a,b) where
- members = [ (x,y) | x<-members, y<-members ]
-
- In order to overcome any problems with unresolved overloading, explicit
- type signatures are often needed to resolve overloading:
-
- ? members :: [Bool]
- [False, True]
- (6 reductions, 26 cells)
- ? length (members :: [((Bool,Bool),(Bool,Bool))])
- 16
- (103 reductions, 195 cells)
- ?
-
- In some cases, the required overloading is implicit from the context
- and no additional type information is required, as in the following
- example:
-
- ? [ x && y | (x,y) <- members ]
- [False, False, False, True]
- (29 reductions, 90 cells)
- ?
-
- We can also use the technique of passing a `dummy' parameter to resolve
- overloading problems in a function definition:
-
- size :: Finite a => a -> Int
- size x = length (members `asTypeOf` [x])
-
- which calculates the number of elements of a finite type, given an
- arbitrary element of that type:
-
- ? size (True,False)
- 4
- (31 reductions, 60 cells)
- ?
-
- Now consider the expression "size (True,False) + size (True,False)".
- At first glance, we expect this to repeat the calculation in the
- previous example two times, requiring approximately twice as many
- reductions and cells as before. However, before this expression is
- evaluated, Gofer constructs a dictionary for Finite (Bool,Bool). The
- evaluation of the first summand forces Gofer to evaluate the value for
- "members" in this dictionary. Since precisely the same dictionary is
- used to calculate the value of the second summand, the evaluation of
- "members" is not repeated and the complete calculation actually uses
- rather fewer reductions and cells:
-
- ? size (True,False) + size (True,False)
- 8
- (51 reductions, 90 cells)
- ?
-
- On the other hand, repeating the original calculation gives exactly the
- same number of reductions and cells as before, because the dictionaries
- constructed at the beginning of each calculation are not retained for
- use in subsequent calculations.
-
- We can force Gofer to construct specific dictionaries whilst reading
- from a file of definitions, so that they are not deleted at the end of
- each calculation, using an explicitly typed variable definition such
- as:
-
- boolBoolMembers = members :: [(Bool,Bool)]
-
- This forces Gofer to construct the dictionary Finite (Bool,Bool) when
- the file of definitions is loaded and prevents it from being deleted at
- the end of each calculation. Having loaded a file containing this
- definition, the first two attempts to evaluate "size (True,False)"
- give:
-
- ? size (True,False)
- 4
- (31 reductions, 60 cells)
- ? size (True,False)
- 4
- (20 reductions, 32 cells)
- ?
-
-
- .ST 14.4.6 The monomorphism restriction
- -----------------------------------
- This section describes a technique used to limit the amount of
- overloading used in the definition of certain values to avoid a number
- of technical problems. This particular topic has attracted quite a lot
- of attention within the Haskell community where it is affectionately
- known as the `dreaded monomorphism restriction'. Although the initial
- formulation of the rule was rather cumbersome and limiting, the current
- version used in both Gofer and Haskell is unlikely to cause any
- problems in practice. In addition, many of the examples used to
- motivate the need for the monomorphism restriction in Haskell occur as
- a result of the use of implicitly overloaded numeric constants,
- described in section 14.4.4, and hence do not occur in Gofer.
-
- The monomorphism restriction takes its name from the way in which it
- limits the amount of polymorphism that can be used in particular kinds
- of declaration. Although we touch on this point in the following
- discussion, the description given here uses an equivalent, but less
- abstract approach, based on observations about the implementation of
- overloaded functions.
-
- Basic ideas:
- ------------
- As we have seen, the implementation of overloading used by Gofer
- depends on being able to add extra arguments to a function definition
- to supply the required dictionary parameters. For example, given a
- function definition such as:
-
- isElement x [] = False
- isElement x (y:ys) = x==y || isElement x ys
-
- we first add a dictionary parameter for the use of the overloaded (==)
- operator on the right hand side, obtaining:
-
- isElement x [] = False
- isElement x (y:ys) = (==) d x y || isElement x ys
-
- Finally, we have to add the variable d as a new parameter for the
- function isElement, on both the left and right hand sides of the
- definition:
-
- isElement d x [] = False
- isElement d x (y:ys) = (==) d x y || isElement d x ys
-
- The monomorphism restriction imposes conditions which prevent this last
- step from being used for certain kinds of value binding.
-
- .cc 5
- Declaration groups:
- -------------------
- Before giving the full details, it is worth pointing out that, in
- general, the monomorphism restriction affects groups of value
- declarations rather than just individual definitions. To illustrate
- this point, consider the function definitions:
-
- f x y = x==y || g x y
- g x y = not (f x y)
-
- Adding an appropriate dictionary parameter for the (==) operator gives:
-
- f x y = (==) d x y || g x y
- g x y = not (f x y)
-
- The next stage is to make this dictionary variable into an extra
- parameter to the function f wherever it appears, giving:
-
- f d x y = (==) d x y || g x y
- g x y = not (f d x y)
-
- But now the right hand side of the second definition mentions the
- dictionary variable d which must therefore be added as an extra
- parameter to g:
-
- f d x y = (==) d x y || g d x y
- g d x y = not (f d x y)
-
- In other words, if dictionary parameters are added to any particular
- function definition, then each use of that function in another
- definition will also be require extra dictionary parameters. As a
- result, the monomorphism restriction has to be applied to the smallest
- groups of declarations such that any pair of mutually recursive
- bindings are in the same group.
-
- As the example above shows, if one (or more) of the bindings in a given
- declaration group is affected by the monomorphism restriction so that
- the appropriate dictionary parameters cannot be added as parameters for
- that definition, then the same condition must also be imposed on all of
- the other bindings in the group. [Adding the extra parameter to f in
- the example forces us to add an extra parameter for g; if extra
- parameters were not permitted for g then they could not be added to f.]
-
- .cc 5
- Restricted bindings:
- --------------------
- There are three main reasons for avoiding adding dictionary parameters
- to a particular value binding:
-
- o Dictionary parameters unnecessary. If the dictionary values are
- completely determined by context then it is not necessary to pass
- the appropriate values as dictionary parameters. For example, the
- function definition:
-
- f x = x == 0 || x == 2
-
- can be translated as:
-
- f x = (==) {dict} x 0 || (==) {dict} x 2
-
- where, in both cases, the symbol {dict} denotes the dictionary for
- Eq Int. As a further optimisation, once the dictionary is fully
- determined, this can be simplified to:
-
- f x = primEqInt x 0 || primEqInt x 2
-
- o Dictionary parameters cannot be added in a pattern binding. One
- potential solution to this problem would be to replace the pattern
- binding by an equivalent set of function bindings. In practice,
- we do not use this technique because it typically causes ambiguity
- problems, as illustrated by the pattern binding:
-
- (plus,times) = ((+), (*))
-
- Translating this into a group of function bindings gives:
-
- newVariable = ((+), (*))
- plus = fst newVariable -- fst (x,_) = x
- times = snd newVariable -- snd (_,y) = y
-
- The type of newVariable is (Num a, Num b) => (a->a->a, b->b->b) so
- that the correct translation of these bindings using two
- dictionary variables gives:
-
- newVariable da db = ((+) da, (*) db)
- plus da db = fst (newVariable da db)
- times da db = snd (newVariable da db)
-
- and hence the correct types for plus and times are:
-
- plus :: (Num a, Num b) => a -> a -> a
- times :: (Num a, Num b) => b -> b -> b
-
- both of which are ambiguous.
-
- o Adding dictionary parameters may translate a variable definition
- into a function definition, loosing the benefits of shared
- evaluation. As an example, consider the following definition
- using the function "size" and the class Finite described in the
- previous section:
-
- twiceSize x = n + n where n = size x
-
- Since the variable n is defined using a local definition, we would
- not expect to have to evaluate size x more than once to determine
- the value of twiceSize. However, adding extra dictionary
- parameters without restriction gives:
-
- twiceSize d x = n d + n d where n d = size d x
-
- Now that n has been replaced by a function, the evaluation will be
- repeated, once for each occurrence of the expression "n d". In
- order to avoid this kind of problem, the monomorphism restriction
- does not usually allow extra parameters to be added to a variable
- definition. Thus the original definition above will be translated
- to give:
-
- twiceSize d x = n + n where n = size d x
-
- Note that the same rule is applied to variable definitions at the
- top-level of a file of definitions, resulting in an error if any
- dictionary parameters are required for the right hand side of the
- definition. As an example of this:
-
- twiceMembers = members ++ members
-
- which produces an error message of the form:
-
- ERROR "ex" (line 157): Unresolved top-level overloading
- *** Binding : twiceMembers
- *** Inferred type : [_7]
- *** Outstanding context : Finite _7
- ?
-
- [COMMENT: A type expression of the form _n (such as _7 in this
- particular example) represents a fixed (i.e. monomorphic) type
- variable.]
-
- In the case of a variable declaration, the monomorphism
- restriction can be overcome by giving an explicit type signature
- including an appropriate context, to indicate that the variable
- defined is intended to be used as an overloaded value. In this
- case, we need only include the declaration:
-
- twiceMembers :: Finite a => [a]
-
- in the file containing the definition for twiceMembers to suppress
- the previous error message and allow the function to be used as a
- fully overloaded variable.
-
- Note that the monomorphism restriction interferes with the use of
- polymorphism. For example, the definition:
-
- aNumber = length (twiceMembers::[Bool]) +
- length (twiceMembers::[(Bool,Bool)])
- where twiceMembers = members ++ members
-
- will not be accepted because the monomorphism restriction forces
- the local definition of "twiceMembers" to be restricted to a
- single overloading (the dictionary parameter supplied to each use
- of members must be constant throughout the local definition):
-
- ERROR "ex" (line 12): Type error in type signature expression
- *** term : twiceMembers
- *** type : [(Bool,Bool)]
- *** does not match : [Bool]
- ?
-
- Once again, this problem can be fixed using an explicit type
- declaration:
-
- aNumber = length (twiceMembers::[Bool]) +
- length (twiceMembers::[(Bool,Bool)])
- where twiceMembers :: Finite a => [a]
- twiceMembers = members ++ members
-
-
- Formal definition:
- ------------------
- The examples above describe the motivation for the monomorphism
- restriction, captured by the following definition:
-
- Dictionary variables will not be used as extra parameters in the
- definition of a value in a given declaration group G if:
-
- either: G includes a pattern binding
-
- or: G includes a variable declaration, but does not include an
- explicit type signature for any of the variables in the
- group.
-
- If neither of these conditions hold, then equivalent sets of dictionary
- parameters will be added to each declaration in the group.
-
- .pa
- .>appx_a
- .ST APPENDIX A: SUMMARY OF GRAMMAR
-
- This section gives a summary of the grammar for the language used by
- Gofer. The non-terminals <interp> and <module> describe the syntax of
- expressions that can be entered into the Gofer interpreter and that of
- files of definitions that can be loaded into Gofer respectively.
-
- The following notational conventions are used in the Grammar which is
- specified using a variant of BNF:
-
- o <angle brackets> are used to distinguish names of nonterminals from
- keywords.
-
- o vertical | bars are used to separate alternatives.
-
- o {braces} enclose items which may be repeated zero or more times.
-
- o [brackets] are used for optional items.
-
- o (parentheses) are used for grouping.
-
- o "quotes" surround characters which might otherwise be confused with
- the notations introduced above.
-
- The following terminal symbols are used but not defined by the grammar:
-
- VARID identifier beginning with lower case letter as described in
- section 6.
- CONID like VARID, but beginning with upper case letter.
- VAROP operator symbol not beginning with a colon, as described in
- section 6.
- CONOP constructor function operator, like VAROP, but beginning
- with a colon character.
- INTEGER integer constant, as described in section 7.3.
- FLOAT floating point constant, as described in section 7.4.
- CHAR character constant, as described in section 7.5.
- STRING string constant, as described in section 7.7.
-
-
- Top-level grammar
- -----------------
-
- <module> ::= "{" <topdecls> "}" module
-
- <interp> ::= <exp> [<where>] top-level expression
-
- <topdecls> ::= <topdecls>; <topdecls> multiple declarations
- | data <typeLhs> = <constrs> datatype declaration
- | type <typeLhs> = <type> synonym declaration
- | infixl [<digit>] <op> {, <op>} fixity declarations
- | infixr [<digit>] <op> {, <op>}
- | infix [<digit>] <op> {, <op>}
- | primitive <prims> :: <type> primitive bindings
- | <class> class declaration
- | <inst> instance declaration
- | <decls> value declarations
-
- <typeLhs> ::= CONID {VARID} type declaration lhs
-
- <constrs> ::= <constrs> "|" <constrs> multiple constructors
- | <type> CONOP <type> infix constructor
- | CONID {<type>} constructor, n>=0
-
- <prims> ::= <prims>, <prims> multiple bindings
- | <var> <string> primitive binding
-
- Type expressions
- ----------------
-
- <sigType> ::= [<context> => ] <type> [qualified] type
-
- <context> ::= "(" [<pred> {, <pred>}] ")" general form
- | <pred> singleton context
- <pred> ::= CONID <type> {<type>} predicate
-
- <type> ::= <ctype> [ -> <type> ] function type
- <ctype> ::= CONID {<atype>} datatype or synonym
- | <atype>
- <atype> ::= VARID type variable
- | "(" ")" unit type
- | "(" <type> ")" parenthesised type
- | "(" <type>,<type> {,<type>} ")" tuple type
- | "[" <type> "]" list type
-
- Class and instance declarations
- -------------------------------
-
- <class> ::= class [<context> =>] <pred> [<cbody>]
- <cbody> ::= where "{" <cdecls> "}" class body
- <cdecls> ::= <cdecls>; <cdecls> multiple declarations
- | <var> {, <var>} :: <type> member functions
- | <fun> <rhs> [<where>] default bindings
-
- <inst> ::= instance [<context> =>] <pred> [<ibody>]
- <ibody> ::= where "{" <idecls> "}" instance body
- <idecls> ::= <idecls>; <idecls> multiple declarations
- | <fun> <rhs> [<where>] member definition
-
- Value declarations
- ------------------
-
- <decls> ::= <decls>; <decls> multiple declarations
- | <var> {, <var>} :: <sigType> type declaration
- | <fun> <rhs> [<where>] function binding
- | <pat> <rhs> [<where>] pattern binding
-
- <rhs> ::= = <exp> simple right hand side
- | <gdRhs> {<gdRhs>} guarded right hand sides
-
- <gdRhs> ::= "|" <exp> = <exp> guarded right hand side
-
- <where> ::= where "{" <decls> "}" local definitions
-
- <fun> ::= <var> function of arity 0
- | <pat> <varop> <pat> infix operator
- | "(" <pat> <varop> ")" section-like notation
- | "(" <varop> <pat> ")"
- | <fun> <apat> function with argument
- | "(" <fun> ")" parenthesised lhs
-
- Expressions
- -----------
-
- <exp> ::= \ <apat> {<apat>} -> <exp> lambda expression
- | let "{" <decls> "}" in <exp> local definition
- | if <exp> then <exp> else <exp> conditional expression
- | case <exp> of "{" <alts> "}" case expression
- | <opExp> :: <sigType> typed expression
- | <opExp>
- <opExp> ::= <opExp> <op> <opExp> operator application
- | <pfxExp>
- <pfxExp> ::= - <appExp> negation
- | <appExp>
- <appExp> ::= <appExp> <atomic> function application
- | <atomic>
- <atomic> ::= <var> variable
- | <conid> constructor
- | INTEGER integer literal
- | FLOAT floating point literal
- | CHAR character literal
- | STRING string literal
- | "(" ")" unit element
- | "(" <exp> ")" parenthesised expr.
- | (<exp> <op>) sections
- | (<op> <exp>)
- | "[" <list> "]" list expression
- | "(" <exp>, <exp> {, <exp>} ")" tuple
-
- <list> ::= [ <exp> {, <exp>} ] enumerated list
- | <exp> "|" <quals> list comprehension
- | <exp> .. arithmetic sequence
- | <exp>, <exp> ..
- | <exp> .. <exp>
- | <exp>, <exp> .. <exp>
- <quals> ::= <quals>, <quals> multiple qualifiers
- | <pat> <- <exp> generator
- | <pat> = <exp> local definition
- | <exp> boolean guard
-
- <alts> ::= <alts>; <alts> multiple alternatives
- | <pat> <altRhs> [<where>] alternative
- <altRhs> ::= -> <exp> single alternative
- | <gdAlt> {<gdAlt>} guarded alternatives
- <gdAlt> ::= "|" <exp> -> <exp> guarded alternative
-
- Patterns
- --------
-
- <pat> ::= <pat> <conop> <pat> operator application
- | <var> + <integer> (n+k) pattern
- | <appPat>
- <appPat> ::= <appPat> <apat> application
- | <apat>
- <apat> ::= <var> variable
- | <var> @ <pat> as pattern
- | ~ <pat> irrefutable pattern
- | _ wildcard
- | <conid> constructor
- | INTEGER integer literal
- | CHAR character literal
- | STRING string literal
- | "(" ")" unit element
- | "(" <pat> ")" parenthesised expr.
- | (<pat> <conop>) sections
- | (<conop> <pat>)
- | "[" [ <pat> {, <pat>} ] "]" list
- | "(" <pat>, <pat> {, <pat>} ")" tuple
-
- Variables and operators
- -----------------------
-
- <var> ::= <varid> | "(" - ")" variable
- <op> ::= <varop> | <conop> | - operator
-
- <varid> ::= VARID | "(" VAROP ")" variable identifier
- <varop> ::= VAROP | ` VARID ` variable operator
-
- <conid> ::= CONID | "(" CONOP ")" constructor identifier
- <conop> ::= CONOP | ` CONID ` constructor operator
-
- .pa
- .>appx_b
- .ST APPENDIX B: CONTENTS OF STANDARD PRELUDE
-
- .in ../standard.prelude
- .pa
- .>appx_c
- .ST APPENDIX C: RELATIONSHIP WITH HASKELL 1.1
-
- The language supported by Gofer is both syntactically and semantically
- similar to that of the functional programming language Haskell as
- defined in the report for Haskell version 1.1 [5]. This section
- details the differences between the two languages, outlined briefly in
- section 2.
-
- .cc 5
- Haskell features not included in Gofer:
- ---------------------------------------
- o Modules
-
- o Arrays
-
- o Derived instances for standard classes -- the ability to construct
- instances of particular classes automatically.
-
- o Default mechanism for eliminating unresolved overloading involving
- numeric and standard classes. Since Gofer is an experimental
- system, it can be used with a range of completely different
- prelude files; there is no concept of `standard classes'.
-
- o Overloaded numeric constants. In the absence of a defaulting
- mechanism as mentioned in the previous item, problems with
- unresolved overloading make implicitly typed programming involving
- numeric constants impractical in an interpreter based system.
-
- o Full range of numeric types and classes. Gofer has only two
- primitive numeric types Int and Float (the second of which is not
- supported in the PC version). Although is would be possible to
- modify the standard prelude so that Gofer uses the same class
- hierarchy as Haskell, this is unnecessarily sophisticated for the
- intended uses of Gofer.
-
- o Datatype definitions in Haskell may involve class constraints such
- as:
-
- data Ord a => Set a = Set [a]
-
- It is not clear how such constraints should be interpreted
- (particularly in the light of the extended form of constraints
- used by Gofer) in such a way to make them useful whilst avoiding
- unwanted ambiguity problems.
-
-
- .cc 5
- Gofer features not supported in Haskell:
- ----------------------------------------
- o Type classes may have multiple parameters.
-
- o Predicates in type expressions may involve arbitrary type
- expressions, not just type variables as used in Haskell.
-
- o Instances of type classes can be defined at non-overlapping, but
- otherwise arbitrary types, as described in section 14.2.5.
-
- o List comprehensions may include local definitions, specified by
- qualifiers of the form <pat>=<expr> as described in section 10.2.
-
- o No restrictions are placed on the form of predicates that appear
- in the context for a class or instance declaration. This has a
- number of consequences, including the possibility of using
- (mutually) recursive groups of dictionaries, but means that
- decidability of the predicate entailment relation may be lost.
- This is not a great problem in practice, since all dictionary
- construction is performed before evaluation and supposedly
- non-terminating dictionary constructions will actually generate an
- error due to the limited amount of space available for holding
- dictionaries (see section 14.4.2).
-
-
- .cc 5
- Other differences:
- ------------------
- o Whilst superficially similar the approach to type classes in Gofer
- is quite different from that used in Haskell. In particular, the
- approach used in Gofer ensures that all necessary dictionaries are
- constructed before the evaluation of an expression begins, rather
- than being built (possibly several times) during the evaluation as
- is the case with Haskell. See section 14 and reference [11] for
- further details.
-
- o Input/Output facilities - Gofer supports only a subset of the
- requests available in Haskell. In principle, it should not be too
- difficult to add most of the remaining forms of request (with the
- exception of those associated with binary files) to Gofer. The
- principal motivation for including the I/O facilities in Gofer was
- to make it possible to experiment with simple interactive
- programs.
-
- o In Gofer, unary minus has greater precedence than any operator
- symbol, but lower than that of function application. In Haskell,
- the precedence of unary minus is the same as that of the infix
- (subtraction) operator of the same name.
-
- o In Haskell, the character `-' can only be used as the first
- character of an operator symbol. In Gofer, this character may
- appear in any position in an operator (except for symbols
- beginning with "--", which indicates the start of a comment). The
- only problems that I am aware of with this is that a lambda
- expression such as "\-2->2" will be parsed as such by a Haskell
- system, but cause a syntax error in Gofer. This form of lambda
- expression is sufficiently unusual that I do not believe this will
- cause any problems in practice; in any case, the parsing problem
- can be solved by inserting a space: "\ -2->2".
-
- o Pattern bindings are not currently permitted in either instance or
- class declarations. This restriction has been made simply for
- ease of implementation, is not an inherent problem with the type
- class system and is likely to be relaxed in later versions of
- Gofer if appropriate. I have yet to see any examples in which the
- lack of pattern bindings in class and instance declarations causes
- any kind of deficiency.
-
- o Qualified type signatures are not permitted for the member
- functions in Gofer class declarations. Once again, this
- restriction was made for ease of implementation rather than any
- pressing technical issues. It is likely that this restriction
- will be relaxed in future versions of Gofer, although I am not
- convinced that proper use can be made of such member functions
- without some form of nested instance declarations (yuk!).
-
- o The definition of the class Text given in the standard prelude
- does not include the Haskell functions for reading/parsing values
- from strings; the only reason for omitting these functions was to
- try to avoid unnecessary complexity in the standard prelude. The
- standard prelude can be modified to include the appropriate
- additional definitions if these are required.
-
-
- .cc 5
- Known problems in Gofer:
- ------------------------
- o The null escape sequence "\&" is not generated in the printable
- representations of strings produced by both the primitive function
- primPrint (used to implement the show' function) and the version
- of show defined in the standard prelude. This means that certain
- strings values are not printed correctly e.g. show' "\245\&123"
- produces the string "\245123". This is unlikely to cause too many
- problems in practice.
-
- o Unification of a type variable a with a type expression of the
- form T a where T is a synonym name whose expansion does not
- involve a will fail. It is not entirely clear whether this
- behaviour is correct or not.
-
- o Formfeeds '\f' and vertical tabs '\v' are not treated as valid
- whitespace characters in the way suggested by the Haskell report.
-
- o Inability to recover from program stack overlow errors in some
- situations. This problem only affects the PC implementation of
- Gofer.
-
- o Implementation of ReadFile may lose referential transparency; the
- response to a particular ReadFile request may be affected by a
- later WriteFile or AppendFile request for the same file. Whilst
- this problem can be solved for UNIX based implementations, I have
- not yet found a portable solution suitable for all of the systems
- on which Gofer can be used.
-
-
- .cc 5
- Areas for possible future improvement:
- --------------------------------------
- o Relaxing the restriction on type synonyms in predicates.
-
- o General purpose automatic default mechanism for eliminating
- certain forms of unresolved overloading.
-
- o Improved checking and use of superclass and instance constraints
- during static analysis and type checking.
-
- o Simple facility to force dictionary construction at load-time.
-
- o Provision for shell escapes :! etc within the Gofer interpreter.
-
- o Debugging facilities, including breakpoints and tracing from
- within interpreter.
-
- o Separate interpreter and compiler programs for creating standalone
- applications using Gofer.
- .pa
- .>appx_d
- .ST APPENDIX D: USING GOFER WITH BIRD+WADLER
-
- Bird and Wadler's textbook [1] gives an excellent introduction to
- functional programming, providing an insight into both basic techniques
- and matters of programming style as well as describing the underlying
- mathematics and its use for program development and derivation. Most
- of the programs in that book can be used with Gofer although there are
- a number of differences between the two notations. Fortunately, it is
- not difficult to translate from one notation to the other. The
- following points are particularly useful for this:
-
- o Type constructors in Gofer begin with capital letters (e.g. Bool,
- Char etc..) where lower case is used in [1] (e.g. bool, char,
- etc..). Note that Gofer has no general numeric type "num" as used
- in [1]; Use either Int, Float, or overloading in Gofer as
- appropriate.
-
- o Datatype definitions in [1] are written in the form lhs::=constrs.
- The equivalent definition in Gofer is: data lhs = constrs.
-
- Similarly, a type synonym definition in [1] of the form lhs == rhs
- can be written in Gofer as: type lhs = rhs.
-
- o The differences between the syntax used for guarded equations in
- Gofer compared with the notation used in [1] have already been
- discussed in section 9.2. For example:
-
- Using the notation of [1]: Using Gofer:
-
- filter p (x:xs) filter p (x:xs)
- = x : filter p xs, if p x | p x = x : filter p xs
- = filter p xs, otherwise | otherwise = filter p xs
-
- o In Gofer, list comprehension qualifiers are separated by commas
- rather than semicolons as used in [1].
-
- o A number of the function names and types in the standard prelude
- are different:
-
- [1] Gofer [1] Gofer
- --- ----- --- ----
- (#) length takewhile takeWhile
- (~) not dropwhile dropWhile
- (/\) (&&) zipwith zipWith
- (\/) (||) swap flip
- (!) (!!) in elem
- (--) (\\) scan scanl
- hd head some any
- tl tail listmin minimum
- decode chr listmax maximum
- code ord
-
- See appendix B for a complete list of standard functions in Gofer.
-
- The version of foldl using "strict" which appears in [1] is
- available in Gofer as the function "foldl'".
-
- The role of "zip" and "zipwith" in [1] is filled by the "zip" and
- "zipWith" families of functions in Gofer. An expression of the
- form "zip (xs,ys)" in [1] is equivalent to "zip xs ys" in Gofer
- etc...
-
- o Gofer does not enforce the condition assumed in [1] that the left
- hand sides of each of the equations defining a function must be
- disjoint.
-
- o The equality operator in Gofer is written as "==" and the single
- equality character "=" is a reserved symbol used to separate left
- and right hand sides of equations. Many C programmers will be
- familiar with this kind of notation (together with the kinds of
- problems it can create!).
-
- o Some of the identifiers used in [1] are reserved words in Gofer.
- Examples that are particularly likely to occur include "in" and
- "then".
- .pa
- .>appx_e
- .ST APPENDIX E: PRIMITIVES
-
- [WARNING: The features described in this appendix are typically only
- needed when alternative versions of the standard prelude are created.
- These features should only be used by expert users; misuse may lead to
- failure and runtime errors in the Gofer interpreter. It is not usually
- a good idea to use primitive functions directly in your programs.]
-
- A number of primitive functions are builtin to the Gofer interpreter,
- and may be bound to function symbols using a declaration of the form:
-
- primitive name1 code1, name2 code2, ...., namen coden :: type
-
- where each name is an identifier (or an operator symbol enclosed by
- parentheses) and each code is a string literal taken from the table
- below. The type specified to the right of the :: symbol must be a
- valid type for the functions being defined -- WARNING: GOFER DOES NOT
- ATTEMPT TO CHECK FOR SUITABILITY OF THE DECLARED TYPE. The following
- definition, taken from the standard prelude, illustrates the use of
- this feature to bind a function named primPrint to the primitive
- function with code name string "primPrint" and type Int -> a -> String
- -> String:
-
- primitive primPrint "primPrint" :: Int -> a -> String -> String
-
- The primitive functions currently available are:
-
- category code name string type
- -------- ---------------- ----
-
- integer primPlusInt Int -> Int -> Int
- arithmetic primMinusInt Int -> Int -> Int
- primMulInt Int -> Int -> Int
- primDivInt Int -> Int -> Int
- primModInt Int -> Int -> Int
- primRemInt Int -> Int -> Int
- primNegInt Int -> Int -> Int
-
-
- floating primPlusFloat Float -> Float -> Float
- point primMinusFloat Float -> Float -> Float
- arithmetic primMulFloat Float -> Float -> Float
- primDivFloat Float -> Float -> Float
- primNegFloat Float -> Float -> Float
-
-
- coercion primIntToChar Int -> Char -- chr in the standard prelude
- functions primCharToInt Char -> Int -- ord in the standard prelude
- primIntToFloat Int -> Float -- implements fromInteger
-
- equality primEqInt Int -> Int -> Bool
- and <= primLeInt Int -> Int -> Bool
- primitives primEqFloat Float -> Float -> Bool
- primLeFloat Float -> Float -> Bool
-
-
- .cc 5
- generic primGenericEq a -> a -> Bool
- ordering primGenericNe a -> a -> Bool
- primitives primGenericGt a -> a -> Bool
- primGenericLe a -> a -> Bool
- primGenericGe a -> a -> Bool
- primGenericLt a -> a -> Bool
-
- These functions implement the standard generic (i.e. non
- overloaded) ordering primitives. They are not currently
- used in the standard prelude. A simplified prelude may be
- created by binding the standard operator symbols (==),
- (/=), (>), (<=), (>=) and (<) to these functions
- respectively.
-
- output primPrint Int -> a -> String -> String
-
- This function is used to implement the show' function in
- the standard prelude and is not usually used directly.
-
- primPrint d e s produces a textual representation of the
- value of the expression e as a string, followed by the
- string s. The integer parameter d is used as an indicator
- of the current precedence level. The primPrint function
- is the standard method of printing the value of an
- expression whose type is not equivalent to the type String
- used by the top-level of the Gofer interpreter.
-
- sequencing primStrict (a -> b) -> a -> b
-
- The primStrict function (bound to the identifier "strict"
- in the standard prelude) forces the evaluation of its
- second argument before the function supplied as the first
- argument is applied to it. See section 9.4 for an
- illustration.
-
- .pa
- .>appx_f
- .ST APPENDIX F: INTERPRETER COMMAND SUMMARY
-
- Command Description
- ------- -----------
- <expr> Analyse expression for errors, typecheck and evaluate. If
- the expression has type Dialogue, execute as a program
- using the I/O facilities as described in section 12. If
- the expression has type String, evaluate and print result
- as a lazy list of characters. In any other case, the
- standard prelude function show' is applied to the
- expression and used to print the value of the result in
- the form of a string, as in the previous case.
-
- :t <expr> Analyse expression for errors, typecheck and print the
- :type <expr> translation and inferred type of the term.
- :T
-
- :q Exit Gofer interpreter.
- :quit
- :Q
-
- :? Display summary of interpreter commands.
- :h
- :H
-
- :l f1 .. fn Removes any previously loaded files of definitions and
- attempts to load the contents of the files f1 upto fn one
- after the other.
-
- :L Remove any previously loaded files of definitions. Only
- those functions and values defined in the standard prelude
- will still be be available.
-
- :load Equivalent forms of the :l command.
- :L
-
- :a f1 .. fn Load the contents of the files f1 upto fn in addition to
- any previously loaded files. If any of the files of
- definitions which have already been loaded have been
- modified since they were last read then they are
- automatically reloaded before any of the files f1 upto fn
- are read.
-
- If successful, a command of the form ":l f1 ..fn" is
- equivalent to the sequence of commands:
- :l
- :a f1
- .
- .
- :a fn
-
- :also Equivalent forms of the :a command.
- :A
-
- :r Repeat the last load command, attempting to reload any
- files which have subsequently been modified. Since later
- files may depend on the definitions in earlier ones, once
- one file has been reloaded, all subsequent files will also
- need to be reloaded.
-
- :reload Equivalent forms of the :r command.
- :R
-
- :e file Suspend current Gofer session and start an editor program
- to modify or view the named file. The Gofer session will
- be resumed when the editor program terminates, and any
- script files that have been changed will be reloaded
- automatically.
-
- Note that a separate editor program is required and that
- Gofer must be properly installed to use this feature. The
- default editor is usually vi (Calvin version 2.0 is a good
- substitute for a PC), although this may have been changed
- when your system was installed. In any case, you can
- always substitute an editor of your choice by setting the
- environment variable EDITOR to the name of your favourite
- editor program.
-
- There are a number of factors which will affect your
- choice of editor. On a slow machine, with only a limited
- amount of memory, you will probably need to choose a
- relatively small editor which can be loaded reasonably
- quickly and does not require too much memory. On a more
- powerful system, you may find it more convenient to use
- Gofer from a window based environment, running your editor
- in one window with Gofer in another.
-
- :e Using the :e command without specifying a particular file
- to be edited starts up an editor program as described
- above either for the file of definitions most recently
- loaded into Gofer or, if an error occurred whilst loading
- a file of definitions, for the file of definitions in
- which the error was last detected.
-
- With many editor programs, it is even possible to start
- the editor at the line where the error occurred. As
- before, it is possible to change the default behaviour of
- Gofer in this case by setting the environment variable
- EDITLINE to a command string which can be used to start
- the editor program with a given file at a specific line
- number. The positions in the string at which the file
- name and line number values should be inserted should be
- indicated by the strings "%s" and "%d" respectively, and
- may appear in either order. The default command string,
- which is used if EDITLINE is not set is "vi +%d %s".
-
- :edit Equivalent forms of the :e command.
- :E
- .pa
- .>appx_g
- .ST APPENDIX G: BIBLIOGRAPHY
-
- [1] Introduction to functional programming, Richard Bird and Philip
- Wadler, Prentice Hall International, 1989.
-
- [2] The Implementation of functional programming languages, Simon L.
- Peyton Jones, Prentice Hall International, 1987.
-
- [3] Lambda Lifting: Transforming Programs to Recursive Equations,
- Thomas Johnsson, in Lecture Notes in Computer Science 201,
- Springer Verlag, 1985. [but try to get a copy of the version of
- this paper included in Johnsson's thesis which benefits from an
- extended typeface and is a little easier to read!]
-
- [4] How to make ad-hoc polymorphism less ad-hoc, Philip Wadler and
- Stephen Blott, University of Glasgow, in the proceedings of the
- 16th ACM annual symposium on Principles of Programming Languages,
- Austin, Texas, January 1989.
-
- [5] Report on the programming language Haskell, a non-strict purely
- functional language (Version 1.1), Paul Hudak, Philip Wadler et
- al. Technical report Yale University/Glasgow University. August,
- 1991.
-
- [6] Introduction to Orwell 6.00, Philip Wadler and Quentin Miller,
- University of Oxford, 1990.
-
- [7] Lazy ML user's manual, Lennart Augustsson and Thomas Johnsson,
- 1990.
-
- [8] Computing with lattices: An application of type classes, Mark P.
- Jones, Technical report PRG-TR-11-90, Programming Research Group,
- Oxford University Computing Laboratory, June 1990.
-
- [9] Towards a theory of qualified types, Mark P. Jones, Technical
- report PRG-TR-6-91, Programming Research Group, Oxford University
- Computing Laboratory, April 1991.
-
- [10] Type inference for qualified types, Mark P. Jones, Technical
- report PRG-TR-10-91, Programming Research Group, Oxford University
- Computing Laboratory, June 1991.
-
- [11] A new approach to type classes, Mark P. Jones, distributed to
- Haskell mailing list 1991.
-
- [12] Practical issues in the implementation of qualified types, Mark P.
- Jones, Forthcoming 1991.
-